What do you use macros for?

382 views
Skip to first unread message

David Storrs

unread,
Apr 6, 2016, 8:19:07 PM4/6/16
to Racket Users
Hi folks,

Macros are one of the biggest features that people list as the advantages of LISP / Scheme, and I don't really understand them.  I get the basics -- they can create new code structures -- but not the implications  What have you used them for, and what do they do that a function couldn't?  What are good times to use them, and what are their drawbacks?

Dave

Asumu Takikawa

unread,
Apr 6, 2016, 8:39:51 PM4/6/16
to David Storrs, Racket Users
Hi David,
There are three "canonical" uses of macros that people often talk about. The
main reference for this idea is an e-mail by Matthias on the LL1 list:

http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01539.html

(quoted below)

At Sat, 25 May 2002 11:03:02 -0400 (EDT), Matthias Felleisen wrote:
> I'd like to propose that there are three disciplined uses of macros:
>
> 1. data sublanguages: I can write simple looking expressions and
> create complex nested lists/arrays/tables with quote, unquote etc
> neatly dressed up with macros.
>
> 2. binding constructs: I can introduce new binding constructs with
> macros. That helps me get rid of lambda's and with placing things
> closer together that belong together. For example, one of our teachpacks
> contains a form
> (web-query ([last-name
> (string-append "Hello " first-name " what's your last name?"])
> ... last-name ... first-name ...)
> with the obvious interaction between a program and a Web consumer implied.
> [Note: In ML you could write
> web-query(fn last-name => ...)string_append(...)
> but by golly that's a pain and an unnecessary pattern.]
>
>
> 3. evaluation reordering: I can introduce constructs that delay/postpone
> the evaluation of expressions as needed. Think of loops, new conditionals,
> delay/force, etc.
> [Note: In Haskell, you don't need that one.]
>
> I understand that Lispers use macros for other reasons. In all honesty, I
> believe that this is partly due to compiler deficiencies, and partly due to
> "semantic" irregularities in the target language.
>
> I challenge people to address all three issues when they say language X
> can do what macros can do.

I think Modern Racket may have additional uses for macros that don't quite fit
into these. For example, the use of macros that expand into submodules in order
to provide metadata for other programs/tools.

Cheers,
Asumu

David Storrs

unread,
Apr 6, 2016, 11:59:13 PM4/6/16
to Asumu Takikawa, Racket Users
Nice.  Thank you, Asumu.  That helps.

Rickard Andersson

unread,
Apr 7, 2016, 2:15:34 AM4/7/16
to David Storrs, Racket Users
> What have you used them for?

While it's certainly not the most impactful macro in the world, I was
pretty pleased with being able to do this:

https://github.com/GoNZooo/gonz/blob/master/gonz/define-test.rkt

It's a macro that allows me to bundle expected inputs with expected
outputs for those inputs together with a contract definition, meaning
I have a tighter coupling of test-cases together with definitions of
functions without a bunch of `(module+ test ...)`.

> What do they do that a function couldn't?

In this case we're actually generating the `(module+ test ...)` entries
based on the lists inside the macro, but then simply appending these to
our generated code, meaning they won't be defined in a function (which
they can't), but rather added as if we were doing it manually.

The manual variant for the first example in this file would look as
follows:

(define/contract (square x)
(integer? . -> . integer?)
(expt x 2))

(module+ test
(check-equal? (square 2) 4)
(check-equal? (square 3) 9)
(check-equal? (square 5) 25))

We're skipping straight to defining inputs and outputs, only because we
are using macros.

> What are good times to use them

In this case I wanted to succinctly define test cases for functions and
for this to be tighter coupled to function definitions, but you can't
call `module+` while inside a function, so the only real solution is to
lift that content outside the function definition.

> ... what are their drawbacks?

The obvious drawback here is that this macro is next to useless for
anyone else when they stumble upon it. When you have an actually useful
macro (I hesitate to call this one universally useful, though I like it
myself), this is solved by great documentation facilities, like
Scribble, as well as good macro constructs that will help you guide your
user through proper usage.
> --
> You received this message because you are subscribed to the Google Groups "Racket Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
> For more options, visit https://groups.google.com/d/optout.

David Storrs

unread,
Apr 7, 2016, 12:11:58 PM4/7/16
to Rickard Andersson, Racket Users
That makes sense.  Thank you, Rickard. 

Daniel Prager

unread,
Apr 7, 2016, 4:54:15 PM4/7/16
to Rickard Andersson, David Storrs, Racket Users
On Thu, Apr 7, 2016 at 4:13 PM, Rickard Andersson <rickard.m...@gmail.com> wrote:
> What have you used them for?

https://github.com/GoNZooo/gonz/blob/master/gonz/define-test.rkt

It's a macro that allows me to bundle expected inputs with expected
outputs for those inputs together with a contract definition, meaning
I have a tighter coupling of test-cases together with definitions of
functions without a bunch of `(module+ test ...)`.

That's a neat example, both as motivation for those asking "why macros?" and folks like me who want to get better at writing them.

For a bit of learning-by-doing I was able to easily change Rickard's macro to get a different surface syntax that correctly processes:

(define/cti (square2&add x y)
  contract: 
  (integer? integer? . -> . integer?)

  tests:
  [2 3 -> 13]
  [1 2 -> 5]
  [-4 4 -> 31]

  implementation: 
  (+ (expt x 2) (expt y 2)))

Further challenges:
  • allow the "contract:" and "tests:" sections to be blank, but print a warning
  • add handling for default, keyword and rest parameters
Dan

David Storrs

unread,
Apr 7, 2016, 8:39:41 PM4/7/16
to Daniel Prager, Rickard Andersson, Racket Users
That's very cool, Dan.  Thanks for the example.  (Although, shouldn't -4^2 + 4^2 = 32, not 31?)

If/when you do the 'further challenges' section, could you post the result here?  It looks like it would be useful.

Dave

Norman Gray

unread,
Apr 8, 2016, 7:10:42 AM4/8/16
to Asumu Takikawa, Racket Users

Greetings.

Quoting Asumu quoting Matthias:

>> I'd like to propose that there are three disciplined uses of macros:
>>
>> 1. data sublanguages: I can write simple looking expressions and
>> [...]
>>
>> 2. binding constructs: I can introduce new binding constructs with
>> [...]
>>
>> 3. evaluation reordering: I can introduce constructs that
>> delay/postpone
>> [...]
>> [Note: In Haskell, you don't need that one.]

This is a seriously naive question, because I have only trivial
experience with Haskell (and by extension other lazy languages), but
aren't each of these things that you can do with a lazy language?

If I can control when an expression is evaluated, then I can obviously
do (3), but supposing I can also control the environment within which
it's evaluated, can't I also do (2) and thence, with suitable juggling,
go on to (1)? Asumu's further example of 'use of macros that expand
into submodules in order to provide metadata for other programs/tools'
sounds at least complicated to do lazily, without macros, but not
fundamentally impossible.

Macros are obviously not the same as lazy evaluation (though from the
point of view of the macro expander, perhaps all the post-compilation
stages are 'lazy'), but I'm having a hard time seeing why it's obvious
they're not isomorphic.

I imagine there may be both pragmatic and fundamental semantic reasons
why the two are different.

All the best,

Norman


--
Norman Gray : https://nxg.me.uk
SUPA School of Physics and Astronomy, University of Glasgow, UK

Sam Caldwell

unread,
Apr 8, 2016, 9:03:06 AM4/8/16
to Norman Gray, Asumu Takikawa, Racket Users
I don't see how a lazy language would let me implement my own version of `let`. Care to enlighten me?

- Sam Caldwell

Alex Knauth

unread,
Apr 8, 2016, 9:28:01 AM4/8/16
to Norman Gray, Asumu Takikawa, Racket Users

> On Apr 8, 2016, at 7:10 AM, Norman Gray <nor...@astro.gla.ac.uk> wrote:

>>> I'd like to propose that there are three disciplined uses of macros:
>>>
>>> 1. data sublanguages: I can write simple looking expressions and
>>> [...]
>>>
>>> 2. binding constructs: I can introduce new binding constructs with
>>> [...]
>>>
>>> 3. evaluation reordering: I can introduce constructs that delay/postpone
>>> [...]
>>> [Note: In Haskell, you don't need that one.]
>
> This is a seriously naive question, because I have only trivial experience with Haskell (and by extension other lazy languages), but aren't each of these things that you can do with a lazy language?
>
> If I can control when an expression is evaluated, then I can obviously do (3),

For "evaluation reordering," it means that with macros, you could take a strict language and make it lazy, *or* take a lazy language and make it strict. Or you could probably do other, weirder things that I can't think of right now. Haskell has forms for making specific things strict, but it's not the default. Whatever the default is, you can change it with macros. So in Haskell this category of macros would still exist, but `delay` wouldn't be one of them. I think macros like `do` that that insert monad threading would also be in this category?

> but supposing I can also control the environment within which it's evaluated, can't I also do (2)

In a language with static scope, no, I don't think so. In racket, haskell, and most other languages, you always know at compile time what identifiers are bound. You could never have an identifier that could be bound at runtime, because the compiler would have seen that identifier and raised an error, at compile time.

It's because of this compile-time knowledge that programmers can look at a program without running it, and still understand it. And also it's because of this that DrRacket can look at a program without running it, but still draw check-syntax arrows, or safely rename identifiers, jump to a definition, or lookup documentation.

Since macros that define new binding constructs are normally defined in terms of existing binding constructs, the programmer still has all of this knowledge at compile time, and DrRacket can still provide all those tools. Because of that, both programmers and DrRacket can understand programs.

If this could change at runtime, most of this would go away. But to use laziness to create new binding constructs without macros, that's what you would have to do.

Alex Knauth

George Neuner

unread,
Apr 8, 2016, 9:40:40 AM4/8/16
to racket...@googlegroups.com
On Fri, 08 Apr 2016 12:10:39 +0100, "Norman Gray"
<nor...@astro.gla.ac.uk> wrote:

>Quoting Asumu quoting Matthias:
>
>>> I'd like to propose that there are three disciplined uses of macros:
>>>
>>> 1. data sublanguages: I can write simple looking expressions and
>>> [...]
>>>
>>> 2. binding constructs: I can introduce new binding constructs with
>>> [...]
>>>
>>> 3. evaluation reordering: I can introduce constructs that
>>> delay/postpone
>>> [...]
>>> [Note: In Haskell, you don't need that one.]
>
>This is a seriously naive question, because I have only trivial
>experience with Haskell (and by extension other lazy languages), but
>aren't each of these things that you can do with a lazy language?

Not necessarily. E.g., in Haskell evaluation is on-demand when needed
but the programmer cannot easily control when that is.

[That, incidentally, is one of the reasons why there is a strict
variant of Haskell. (The other is that it is very difficult to reason
about memory usage in a lazy language.)]


>If I can control when an expression is evaluated, then I can obviously
>do (3), but supposing I can also control the environment within which
>it's evaluated, can't I also do (2) and thence, with suitable juggling,
>go on to (1)?

Yes. But control of when an expression is evaluated at runtime is not
a given in any language higher than assembler [though some are much
more predictable than others].


>Macros are obviously not the same as lazy evaluation (though from the
>point of view of the macro expander, perhaps all the post-compilation
>stages are 'lazy'), but I'm having a hard time seeing why it's obvious
>they're not isomorphic.

Macros (at least Lisp and Scheme style macros) can change the nature
of the eventual runtime evaluation. Lazy evaluation can only change
the timing.

George

Jerzy Karczmarczuk

unread,
Apr 8, 2016, 9:49:35 AM4/8/16
to racket...@googlegroups.com
Hi.

Le 08/04/2016 15:40, George Neuner a écrit :
> Macros (at least Lisp and Scheme style macros) can change the nature
> of the eventual runtime evaluation.
What do you mean by the 'nature' of the evaluation? Do you thnk that
macro change the operational semantics of the language, or what?...
Thank you.

Jerzy Karczmarczuk


Robby Findler

unread,
Apr 8, 2016, 10:00:39 AM4/8/16
to Alex Knauth, Norman Gray, Asumu Takikawa, Racket Users
I think this is not quite Matthias's point about laziness. The point
is that Haskell programmers generally (are supposed to) program as if
they don't care what order things are evaluated in, so the ability of
macros to change that order isn't relevant to them. (Of course, this
is foolish for a number of reasons -- serious Haskell programmers to
have to care what order things are evaluated in (cf seq).)

That said, I also think this thread is kind of heading down the wrong
path. This way of thinking about what is important about macros was
the best understanding we had a decade or two ago. I think that our
experience with racket, however, has proven that the right way to
think about the value of macros is to think of them as giving access
to an extensible/open compiler and thus we can more easily design and
build new programming languages. In other words, it's all about
acknowledging that there is no best language so what we really need is
the ability to more easily build new ones that fit new kinds of
computations we want to do.

Robby

George Neuner

unread,
Apr 8, 2016, 10:36:52 AM4/8/16
to racket...@googlegroups.com
On Fri, 8 Apr 2016 15:49:32 +0200, Jerzy Karczmarczuk
<jerzy.kar...@unicaen.fr> wrote:

>Le 08/04/2016 15:40, George Neuner a écrit :
>
>> Macros (at least Lisp and Scheme style macros) can change the nature
>> of the eventual runtime evaluation.
>
>What do you mean by the 'nature' of the evaluation? Do you thnk that
>macro change the operational semantics of the language, or what?...

Lisp and Scheme macros are very much like a compiler in that they can
do arbitrary source to target translation.

They can't change the characteristics of the underlying target [i.e.
the Lisp or Scheme implementation] - but, like a compiler, they can
define and translate to the target a new *source* language which has
different semantics.

George

Matthias Felleisen

unread,
Apr 11, 2016, 10:17:34 AM4/11/16
to racket...@googlegroups.com

Late to the party, and to make life simple, I removed all quoted material.

Asumu quoted me from the early 1990s. That's what I
understood back then about macros and I got on the
wrong track with this idea for about five to eight
years. I wanted macros to become complete language
definitions ... and that's what they are good for,
but there is way more in all directions. So here are
two thoughts.

First credit to Kent Dybvig who bluntly said to me
in '97 or so, I want to be able to define macros inside
of some lexical scope. These days I define little macros
inside of loops or methods all the time.

Here is an example:

(define/public (run)
(parameterize ([current-input-port in] [current-output-port out])
(send-message SIGN-UP)
(define ok (read-json in))
(define/fsm start
(state: start json->start void --> choose)
(state: choose json->choose (compose send-message values) --> feed-next)
(state: feed-next json->state next->json --> feed-next || start))))

The above is a remote proxy that will receive three kinds of
messages on port 'in' and will respond via a call to a proper
class. Then it transitions to a different state where it will wait
for a different kind of input. The define/fsm macro makes this
incredible easy to express. I bet everyone can read this 3-line
FSM and guess what each transition does:

-- in state 'feed-next', the method reads the message with json->state
-- calls the method feed-next of the contained object
-- sends the result off to the 'out' port with next->json
-- and then transitions to either feed-next or start,
depending on what kind of message arrives.

The 10-line macro refers to pieces of the class and cannot be
defined globally.

Second credit to Matthew (and Shriram and Robby) who kept me
on the 'macros are good for designing complete languages' track,
too. This one is important for different reasons. Often you want
to create a reasonably complete language but not invent everything
from scratch. In our words, you want 'linguistic inheritance'. For
example, you like Haskell's type system but you hate, loathe, despise
the stupid idea of universally lazy evaluation. Well, if you were
in Racket, you'd define a 10-line language that looks, feels, smells,
tastes like the host but has a by-value evaluation mechanism. Of course,
Racket is by-value, so you may convert it into a lazy language without
giving up the lovely parentheses and the beautiful Lisp syntax. Well,
it's a 10-line language definition, based on Racket's generalization
of old, very old, stone-age old Lisp macro system.

See the last chapter of Realm of Racket on how to teach Kindergarden
kids to define this kind of by-need (actually by-name) Racket.

In short, as Robby says 'macros' (what a stupid word) serve the whole
range of purposes, from little conveniences inside of a class to entire
language rewrites that keep the best of one language and add better
things on top

-- Matthias







George Neuner

unread,
Apr 11, 2016, 5:53:35 PM4/11/16
to racket...@googlegroups.com
On Mon, 11 Apr 2016 10:25:46 -0400, Matthias Felleisen
<matt...@ccs.neu.edu> wrote:

>These days I define little macros inside of loops or methods all the time.

Same here. My most common uses are to handle database connections and
to embed free form SQL into Racket code.

George

Daniel Prager

unread,
Apr 11, 2016, 6:51:57 PM4/11/16
to George Neuner, Racket Users

Care to post one of your examples, with a bit of commentary?

Dan

Benjamin Greenman

unread,
Apr 11, 2016, 9:01:27 PM4/11/16
to Racket Users
Today I decided to convert all structure definitions in a Typed Racket file to lists. For instance,

(struct foo ([x : Integer]))

would become a tagged list:

(define-type foo (Pairof 'foo (List Integer)))

along with a constructor, predicate, and accessors.

Using a macro, I was able to write what I wanted to do once and use it for multiple structures. 
Here's how it looked in one file: all I had to do was write the macro, use "struct2" instead of "struct", and remove uses of "struct-out". Other files in the project worked with the new data definition, no problems.

#lang typed/racket
(require (for-syntax racket/base syntax/parse racket/syntax))

(define-syntax (struct2 stx)
  (syntax-parse stx #:datum-literals (:)
   [(_ name:id ([f*:id : t*] ...))
    #:with ((name-f* i*) ...)
      (for/list ([f (in-list (syntax-e #'(f* ...)))]
                 [i (in-naturals 1)])
        (list (format-id stx "~a-~a" (syntax-e #'name) (syntax-e f)) i))
    #:with Name (format-id stx "~a" (string-titlecase (symbol->string (syntax-e #'name))))
    #:with name? (format-id stx "~a?" (syntax-e #'name))
    (syntax/loc stx (begin
      (define-type Name (Pairof 'name (Listof Any)))
      (provide Name)
      (define (name (f* : t*) ...) : Name
        (list 'name f* ...))
      (provide name)
      (define (name-f* (p : Name)) : t*
        (cast (list-ref p 'i*) t*))
      ...
      (provide name-f* ...)
      (define (name? (v : Any)) : Boolean
        (and (list? v) (not (null? v)) (eq? 'name (car v))))
  ))]))

(struct2 posn ([x : Real]
               [y : Real]))
(struct2 block ([x : Real]
                [y : Real]
                [color : Symbol]))
(struct2 tetra ([center : Posn]
                [blocks : (Listof Block)]))
(struct2 world ([tetra : Tetra]
                [blocks : (Listof Block)]))

(: posn=? (-> Posn Posn Boolean))
(define (posn=? p1 p2)
  (and (= (posn-x p1) (posn-x p2))
       (= (posn-y p1) (posn-y p2))))

(provide posn=?)


(Nevermind why I did this, or why I used "Any" and "cast". Hopefully the macro is interesting.)


George Neuner

unread,
Apr 12, 2016, 2:51:03 PM4/12/16
to Daniel Prager, racket users
Hi Dan,


On 4/11/2016 6:51 PM, Daniel Prager wrote:

On Apr 12, 2016 7:53 AM, "George Neuner" <gneu...@comcast.net> wrote:
>
> My most common uses are to handle database connections and
> to embed free form SQL into Racket code.

Care to post one of your examples, with a bit of commentary?


Ok.  I have attached the definition file for my SQL embedding macro.  It is a relatively simple minded syntax transformer, but it needs some auxiliary functions so it is inconvenient to repeat it all here.  I hope the attachment survives to the mailing list.

First off, I want to say that the db module in Racket is awesome!  However ... like other database access libraries, it can be inconvenient to compose complex queries because the functions expect SQL code to be provided as a string.

Racket has multi-line string literals (#<<) which are suitable for embedding *static* SQL [if you don't mind delimiters in the margin].  But AFAICS there is no way to splice environment references into a multi-line literal - i.e. quasiquote/unquote(-splicing) - and so they can't easily be used for dynamic SQL.  And there are many needs for dynamic SQL: in particular table names cannot be variables, so queries that are reusable against multiple tables cannot be written statically.

Not to mention that multi-line strings won't auto-indent, and any whitespace you might include for readability ends up in the code string.  SQL ignores whitespace outside of strings, but lots of whitespace in the code possibly lengthens transmission time to a remote DBMS and parse time for the query, and so may impact your application performance (however slightly) as you repeatedly submit overly long code strings.

So you - or at least *I* - end up with a lot of code that looks like:

  (set! sql-cmd (string-join `(  ; <- note quasiquote

           :

           ",business as"
           "   (select " ,(prefix-fields (business-fields) "B")
           "      from (select distinct biz from stores) as S"
           "      join businesses as B on B.id  = S.biz"
              ,(if search-txt
                 "join acts as A on A.biz = S.biz"
                  "" )
           "    )"

           :

           ",ratings (biz,reviews,score) as"
           "   (select B.id"
           "          ,count_reviews( (select * from survey), B.id )"
           "          ,score_survey ( (select * from survey), B.id, 100 )"
           "      from (select id from business) as B"
           "    )"

           :

         )))

  (set! result (query-rows db sql-cmd ... )


These CTEs are snippets from a 73  line query in one of my applications.


Not terribly easy to read even with DrRacket's syntax coloring: just strings and little snips of Racket.  Inefficient because the SQL code string is being composed at runtime.  I have timed string-join: it is quite a bit slower to join lots of short strings than to join fewer long strings.


So I created the code in embed_sql.rkt.  The language it accepts is not SQL precisely but a hybrid of SQL and Racket.  For example, double quoted Racket strings are converted to single quoted SQL strings.  I borrowed Racket's bytestrings to represent SQL strings that should be double quoted: e.g., strange table names.  Because commas are used natively in SQL, I borrowed unquote-splicing ,@ syntax to embed Racket expressions into SQL.  Racket style comments are ignored everywhere.  SQL style -- comments aren't supported.

The mess above can be written [more nicely I think] as:

  (set! sql-cmd
    (SQL
         :
       ,business as
         (select ,@(prefix-fields (business-fields) "B")
            from (select distinct biz from stores) as S
            join businesses as B on B.id  = S.biz
           ,@(if search-txt
              (SQL join acts as A on A.biz = S.biz)
              (SQL))
          )
         :
       ,ratings (biz,reviews,score) as
         (select B.id
                ,count_reviews( (select * from survey), B.id )
                ,score_survey ( (select * from survey), B.id, 100 )
            from (select id from business) as B
          )
         :
     )


The macro turns static SQL into a simple string, and dynamic SQL into a string-join quasiquoted form.  It ignores whitespace and minimizes the number of string fragments passed to the eventual string-join (which will be done at runtime).

The macro can be used wherever a string is expected, so it can be used directly to supply the command argument to a query function call.  It works ad hoc in the REPL assuming any embedded Racket code is valid.

It isn't perfect ... in particular errors occurring in embedded Racket expressions can be hard to figure out.  But it has handled all the SQL I have thrown at it and I have used it in a number of applications.  I'd like eventually to improve it with some syntax coloring, but I haven't figured out how to do that (or even if it's possible).


YMMV.   Everyone may feel free to laugh [privately!] at the code.
George




    
embed_sql.rkt

Daniel Prager

unread,
Apr 12, 2016, 4:45:35 PM4/12/16
to George Neuner, racket users
Thanks George

Of interest to me is that you eschew the use of syntax-parse / -case / -rules in favour of a straight syntax->datum -> straight racket -> datum->syntax solution. I suppose this approach trades away hygiene and error-checking for simplicity.

Also, nice trick appropriating byte-strings:

* * *
I have a similar in spirit (but less sophisticated) macro that embeds mustache-style templating in sxml in conjunction with Jens's Urlang JS replacement in conjunction with Neil's html-writing library.

It currently does two things:
  1. Creates template variables: ($ foo) -> "{{foo}}"
  2. Maps embedded Urlang function calls to JS syntax: ($ (foo bar baz)) -> "foo(bar, baz)"
E.g. this snippet of sxml

  `((h1 "SVG test")
    (div
     (input (@ (type "range") (value ,($ radius))
               (min "10") (max "95") (step "5")))
     (br) (br)
     (button (@ (on-click "swap")) "Change color"))
    
    (svg (@ (width "200") (height "200"))
         (circle (@ (cx "100") (cy "100") (r ,($ radius))
                    (stroke "green") (stroke-width "4")
                    (fill ,($ fill)))))))

maps to

<h1>SVG test</h1>
<div>
  <input type="range" value="{{radius}}" min="10" max="95" step="5">
  <br> <br>
  <button on-click="swap">Change color</button>
</div>
<svg width="200" height="200">
  <circle cx="100" cy="100" r="{{radius}}"
          stroke="green" stroke-width="4" fill="{{fill}}">
  </circle>
</svg>

when passed through (xexp->html ...) and formatted nicely. Here's my little macro ...

(define-syntax ($ stx)
  (syntax-parse stx
    [(_ var:id)
     (with-syntax ([v (datum->syntax stx
                                     (format "{{~a}}" (syntax->datum #'var)))])
       #'v)]
    [(_ (fn:id args:id ...))
     (with-syntax ([js (datum->syntax stx
                                      (format "{{~a(~a)}}"
                                                  (syntax->datum #'fn)
                                                  (string-join
                                                   (map (compose
                                                         (λ (x) (format "~a" x))
                                                         syntax->datum)
                                                        (syntax->list #'(args ...)))
                                                   ", ")))])
       #'js)]))

Compared to George's macro, it uses syntax-parse for pattern-matching rather than match, and still uses datum->syntax / syntax->datum, but more locally within with-syntax.

Dan

Anthony Carrico

unread,
Apr 12, 2016, 4:54:31 PM4/12/16
to racket...@googlegroups.com
On 04/11/2016 10:25 AM, Matthias Felleisen wrote:
> Second credit to Matthew (and Shriram and Robby) who kept me
> on the 'macros are good for designing complete languages' track,
> too. This one is important for different reasons. Often you want
> to create a reasonably complete language but not invent everything
> from scratch. In our words, you want 'linguistic inheritance'. For
> example, you like Haskell's type system but you hate, loathe, despise
> the stupid idea of universally lazy evaluation. Well, if you were
> in Racket, you'd define a 10-line language that looks, feels, smells,
> tastes like the host but has a by-value evaluation mechanism. Of course,
> Racket is by-value, so you may convert it into a lazy language without
> giving up the lovely parentheses and the beautiful Lisp syntax. Well,
> it's a 10-line language definition, based on Racket's generalization
> of old, very old, stone-age old Lisp macro system.
>
> See the last chapter of Realm of Racket on how to teach Kindergarden
> kids to define this kind of by-need (actually by-name) Racket.

I love how Scheme and Racket continue to push the boundary on what can
be done with syntax extension, but these paragraphs are hyperbole. There
is a long way to go before working programmers can make linguistic
inheritance happen. At least a book needs to be written, maybe more
research. Please view Asumu's google hangout on typed racket + the class
system for evidence about just how sticky things are in the real world.
Don't get me wrong, PLT has done, and is doing the work, and that work
is under appreciated by the programming languages community, but
still... really? Let's not over-sell and under-deliver.

--
Anthony Carrico

Matthias Felleisen

unread,
Apr 12, 2016, 5:56:18 PM4/12/16
to Anthony Carrico, racket...@googlegroups.com

On Apr 12, 2016, at 4:54 PM, Anthony Carrico <acar...@memebeam.org> wrote:

 Please view Asumu's google hangout on typed racket 


Asumu is my PhD students and the Kindergarden ought to have been a give-away :-) 

Yes, Racket’s syntax extension system is the second best in the world. And 
I am still young enough to take on PhD students who will improve it .. 

Want to volunteer? 

George Neuner

unread,
Apr 12, 2016, 8:32:10 PM4/12/16
to racket...@googlegroups.com
On Wed, 13 Apr 2016 06:45:01 +1000, Daniel Prager
<daniel....@gmail.com> wrote:

>Thanks George
>
>Of interest to me is that you eschew the use of syntax-parse / -case /
>-rules in favour of a straight syntax->datum -> straight racket ->
>datum->syntax solution. I suppose this approach trades away hygiene and
>error-checking for simplicity.

I have more conventional syntax-rules macros that are mostly for
dealing with the boiler-plate involved in database connections and
transactions.

I only recently learned about syntax-parse but I haven't tried it yet.
Racket has so many features you could spend your life just learning
the distribution. [Only partly jest]


>* * *
>I have a similar in spirit (but less sophisticated) macro that embeds
>mustache-style templating in sxml in conjunction with Jens's Urlang JS
>replacement in conjunction with Neil's html-writing library.

Nice!

George

Reply all
Reply to author
Forward
0 new messages