[racket] Macros and dynamically generating identifiers

319 views
Skip to first unread message

Sean McBeth

unread,
May 2, 2013, 10:29:28 PM5/2/13
to users
Hi there!

I'm pretty new to Racket, though not the basic concepts of functional programming [1] Maybe I don't need macros here at all, but it seemed like the right sort of lever when I first started, but now I'm pretty stuck[2] and I don't understand enough about the macro system yet to be able to figure this out.

Basically, I'm trying to make a database migration tool + relational mapper. I'd like to be able to define my tables in an abbreviated Racket syntax and use the definition to generate everything from the create-table SQL scripts, a few, basic CRUD-scripts-for-all-columns to structs that will mirror a full table row when processing the query results.

Right now, the table definition looks like this:

(define-table tickets get-all-tickets
 ([ticket_id serial primary-key]
  [priority int nullable] ;; I believe in making not-null the default case
  [description (varchar max)]
  [finished_on datetime (default "9999-12-31 23:59:59.999")])

And this is pretty easy to parse into some "table" structs that describe everything fairly sufficiently[3]:
https://gist.github.com/capnmidnight/5506674

Now, my sticking point is that I don't want to have explicitly define that "get-all-tickets" identifier. I notice that, in my creating the "column" struct, I've received a number of procedures for the constructor and field accessors, all given a prefix of "column" for their identifier. So at first glance, it seems like there are forms like struct that are capable of dynamically defining identifiers.

So, I stepped into the definition for struct and tried to make sense of it, but the best I could figure out was that struct used syntax-case instead of syntax-rules. It was a bit of a hair-ball for me, I couldn't suss out the cross references, and at least at this late of an hour I'm having trouble understanding the documentation on syntax-case.

Specifically, I tried to do something like:

(define-syntax (double-define stx)
  (syntax-case stx (double-define)
    [(_ id val1 val2)
     #`(begin (define id-1 val1)
              (define id-2 val2))]))

(double-define id 3 7)
(displayln id-1) ;; error "id-1 unbound identifier"
(displayln id-2)

I then tried something like:

(define-syntax (double-define stx)
  (syntax-case stx (double-define)
    [(_ id val1 val2)
     (with-syntax ([id-1 #'(string->symbol (format "~a-1" id))] ;; error "define: not an identifier, identifier with default, or keyword for procedure argument"
                   [id-2 #'(string->symbol (format "~a-2" id))])
       #'(begin (define id-1 val1)
                (define id-2 val2)))]))

(double-define id 3 7)
(displayln id-1)
(displayln id-2)

Clearly, not correct.

I could make the table struct into a table class and then just define a get-all method that does what I want, but that kind of feels like giving up and I'm more interested in using this to learn more about using macros, as it has already done for me.



[1] Functional C# is something of a job safety program of mine :P

[2] i.e. been banging my head against the desk for the last 6 hours. I have gotten pretty comfortable with syntax-rules though, so it wasn't a complete waste.

[3] This isn't the final form, but I'm just pushing some code around to try to get the basic concepts working. For example, the get-all-tickets procedure wouldn't just return the query, it'd eventually execute it and return the results.

Jay McCarthy

unread,
May 2, 2013, 10:35:08 PM5/2/13
to Sean McBeth, users
You were close to what you want. Here's a version with a nice utility
and then the underlying machinery that makes it:

#lang racket
(require (for-syntax racket/syntax))

(define-syntax (double-define stx)
(syntax-case stx (double-define)
[(_ id val1 val2)
(with-syntax ([id-1 (format-id #'id "~a-1" #'id)]
[id-2 (datum->syntax
#'id
(string->symbol
(format "~a-2"
(syntax->datum
#'id))))])
#'(begin (define id-1 val1)
(define id-2 val2)))]))

(double-define id 3 7)
(displayln id-1)
(displayln id-2)

> ____________________
> Racket Users list:
> http://lists.racket-lang.org/users
>



--
Jay McCarthy <j...@cs.byu.edu>
Assistant Professor / Brigham Young University
http://faculty.cs.byu.edu/~jay

"The glory of God is Intelligence" - D&C 93
____________________
Racket Users list:
http://lists.racket-lang.org/users

Sean McBeth

unread,
May 2, 2013, 10:38:24 PM5/2/13
to Jay McCarthy, users

Aaah, man. Thanks. Shutdown the pc for the night so u will try tomorrow.

Sean McBeth

unread,
May 2, 2013, 10:57:25 PM5/2/13
to Jay McCarthy, users

I think I get it just from reading it (in bed,  on the phone, annoying the wife). I had tried to do almost this very thing with datum->syntax at one point, but I had put the quotesyntax on datum->syntax, not on id directly. I don't understand why that would make a difference, it seems like it is similar to doing (list 'a 'b) instead of '(a b)... oh, nope, now I get it. It specifically *is* similar, except my second example should have been '(list a b).

Philipp Dikmann

unread,
May 3, 2013, 7:35:30 AM5/3/13
to Sean McBeth, Racket Users
You might have already read it, but I also found the guide "Fear of Macros" by Greg Hendershott incredibly helpful in understanding them, especially considering things like with-syntax and format-id:
http://www.greghendershott.com/fear-of-macros/index.html

Sean McBeth

unread,
May 3, 2013, 1:58:19 PM5/3/13
to Philipp Dikmann, Racket Users
Wow, section 4 is exactly, almost word for word, what I was trying to do. This is a great resource.
Reply all
Reply to author
Forward
0 new messages