[racket-users] Need help improving a macro

2017-08-29 Thread hiphish
Hello,

I have a working macro written using `syntax-parse`, but it still has
some kinks that need to be worked out. What I want the macro to do is
define a function and as it as input to another function. Example:

  (define-nvim-function (foo bar baz) #:name "foo" #:sync #f
(display bar)
(display baz))

This would expand to

  (define (foo bar baz)
(display bar)
(display baz))
  (register-nvim-function! foo "foo" #:sync #f)

The `register-nvim-function!` function is defined like this:

  (define (register-nvim-function! proc name
   #:range [range #f]
   #:eval  [eval  (void)]
   #:sync  [sync? #f])
...)

The point is that `proc` and `name` are mandatory, but the keyword
arguments are optional. I want the keyword arguments of the macro to be
optional as well, except for the name, and their order should not
matter. Here is what I got so far:


  (define-syntax (define-nvim-function stx)
(syntax-parse stx
  [(define-function (head:id arg-id:id ...)
(~alt (~once (~seq #:name name:expr)
 #:name "#:name name")
  (~optional (~seq #:range range:expr)
 #:name "#:range range"
 #:defaults ([range #'#f]))
  (~optional (~seq #:eval eval:expr)
 #:name "#:eval eval"
 #:defaults ([eval #'(void)]))
  (~optional (~seq #:sync sync?:expr)
 #:name "#:sync sync?"
 #:defaults ([sync? #'#f])))
...
 body:expr ...+)
   #'(define-function head #:name name #:range range #:eval eval #:sync 
sync?
   (λ (arg-id ...) body ...)) ]
  [(define-function head:id
(~alt (~once (~seq #:name name:expr)
 #:name "#:name name")
  (~optional (~seq #:range range:expr)
 #:name "#:range range"
 #:defaults ([range #'#f]))
  (~optional (~seq #:eval eval:expr)
 #:name "#:eval eval"
 #:defaults ([eval #'(void)]))
  (~optional (~seq #:sync sync?:expr)
 #:name "#:sync sync?"
 #:defaults ([sync? #'#f])))
...
 body:expr)
   #'(begin
   (define head body)
   (register-function! head name #:range range #:eval eval #:sync 
sync?))]))

The macros supports two variants, similar to how `define` allows two
ways of defining a function. The problems with this implementation are:

- The giant blob of keyword arguments is repeated twice
- The template is listing all the keyword arguments the function can
  take

I was thinking of a macro more like this:

  (define-syntax (define-nvim-function stx)
(define-splicing-syntax-class option
  (pattern (#:namename:expr))
  (pattern (#:range range?:expr))
  (pattern (#:eval   eval?:expr))
  (pattern (#:sync   sync?:expr)))
(syntax-parse stx
  [(define-function (head:id arg-id:id ...) option:option ...
 body:expr ...+)
   #'(define-function head option ...
   (λ (arg-id ...) body ...)) ]
  [(define-function head:id option.option ...
 body:expr)
   #'(begin
   (define head body)
   (register-function! head option))]))

This won't work though. First I lose the optional/unique specifications
from above, second the keyword-value pairs are wrapped in parentheses
for some reason. Expansion becomes

  (register-function! foo (#:name "foo") (#:sync #f))
  ;; instead of
  (register-function! foo #:name "foo" #:sync #f)

What am I doing wrong here? I have a pretty solid grasp of Racket thanks
to the great guide, but when it comes to macros I am still totally lost.

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Need help improving a macro

2017-08-29 Thread Philip McGrath
I would love to see some library provide better abstractions for fairly
common cases of keywords like this, but here is how I might write this:

#lang racket

(require (for-syntax syntax/parse
 syntax/define
 ))

(define-syntax (register-nvim-function! stx)
  (displayln stx)
  #`(begin))

(define-syntax (define-nvim-function stx)
  (define-syntax-rule (attr? kw pat-id)
(and (attribute pat-id) (list #'kw #'pat-id)))
  (define-splicing-syntax-class options
#:attributes (name [opt 1])
(pattern (~seq (~alt (~once (~seq #:name name:expr))
 (~optional (~seq #:range range?:expr))
 (~optional (~seq #:eval eval?:expr))
 (~optional (~seq #:sync sync?:expr)))
   ...)
 #:with (opt ...)
 #`(#,@(for*/list ([clause (in-list
(list (attr? #:range range?)
  (attr? #:eval eval?)
  (attr? #:sync sync?)))]
   #:when clause
   [term (in-list clause)])
 term
  (syntax-parse stx
[(_ raw-lhs:expr opts:options
body:expr ...+)
 #:do [(define-values (ident-stx rhs-stx)
 (normalize-definition #'(define raw-lhs body ...)
   #'λ
   #t
   #t))]
 #:with ident ident-stx
 #:with rhs rhs-stx
 #'(begin
 (define ident rhs)
 (register-nvim-function! ident opts.name opts.opt ...))]))

(define-nvim-function (foo bar baz) #:name "foo" #:sync #f
  (display bar)
  (display baz))

You would probably then want to add contracts using `expr/c`.

I also wonder if you really want to have a mandatory "name" argument or if
you should infer it based on the identifier being defined (perhaps with an
option to override).

-Philip

On Tue, Aug 29, 2017 at 11:27 AM,  wrote:

> Hello,
>
> I have a working macro written using `syntax-parse`, but it still has
> some kinks that need to be worked out. What I want the macro to do is
> define a function and as it as input to another function. Example:
>
>   (define-nvim-function (foo bar baz) #:name "foo" #:sync #f
> (display bar)
> (display baz))
>
> This would expand to
>
>   (define (foo bar baz)
> (display bar)
> (display baz))
>   (register-nvim-function! foo "foo" #:sync #f)
>
> The `register-nvim-function!` function is defined like this:
>
>   (define (register-nvim-function! proc name
>#:range [range #f]
>#:eval  [eval  (void)]
>#:sync  [sync? #f])
> ...)
>
> The point is that `proc` and `name` are mandatory, but the keyword
> arguments are optional. I want the keyword arguments of the macro to be
> optional as well, except for the name, and their order should not
> matter. Here is what I got so far:
>
>
>   (define-syntax (define-nvim-function stx)
> (syntax-parse stx
>   [(define-function (head:id arg-id:id ...)
> (~alt (~once (~seq #:name name:expr)
>  #:name "#:name name")
>   (~optional (~seq #:range range:expr)
>  #:name "#:range range"
>  #:defaults ([range #'#f]))
>   (~optional (~seq #:eval eval:expr)
>  #:name "#:eval eval"
>  #:defaults ([eval #'(void)]))
>   (~optional (~seq #:sync sync?:expr)
>  #:name "#:sync sync?"
>  #:defaults ([sync? #'#f])))
> ...
>  body:expr ...+)
>#'(define-function head #:name name #:range range #:eval eval
> #:sync sync?
>(λ (arg-id ...) body ...)) ]
>   [(define-function head:id
> (~alt (~once (~seq #:name name:expr)
>  #:name "#:name name")
>   (~optional (~seq #:range range:expr)
>  #:name "#:range range"
>  #:defaults ([range #'#f]))
>   (~optional (~seq #:eval eval:expr)
>  #:name "#:eval eval"
>  #:defaults ([eval #'(void)]))
>   (~optional (~seq #:sync sync?:expr)
>  #:name "#:sync sync?"
>  #:defaults ([sync? #'#f])))
> ...
>  body:expr)
>#'(begi

Re: [racket-users] Need help improving a macro

2017-08-30 Thread hiphish
> I would love to see some library provide better abstractions for
> fairly common cases of keywords like this
Honestly, I don't think I need a library, I need better knowledge. Your
macro works, but I have no idea how I could have come up with it. The
Racket guide gives a good introduction to the language, but when it
comes to the macro system which is basically a language on its own, all
I get is one chapter in the the guide and a huge reference manual. How
did other people learn Racket meta programming? Read the implementation?
I feel like I have opened a box full of electronics parts and all parts
are labeled and documented perfectly, but I have no idea what any of
those labels mean.

It looks like your macro is turning strings into symbols. I changed

  (define-syntax (register-nvim-function! stx)
(displayln stx)
#`(begin))

into

  (define-syntax (register-nvim-function! stx)
(displayln (syntax->datum stx))
#`(begin))

so that it would print the list-representation of what it got, and all
the strings are symbols:

  > (define-nvim-function (foo bar baz) #:name "_foo" #:eval (void) #:sync 
"hello"
  (display bar)
  (display baz))
  (register-nvim-function! foo _foo #:eval (void) #:sync hello)

This is not much of an issue since symbols are packed into strings, but
it is a weak spot.

> You would probably then want to add contracts using `expr/c`.
Yes, once I can wrap my head around how the macro works I can harden it.

> I also wonder if you really want to have a mandatory "name" argument
> or if you should infer it based on the identifier being defined
> (perhaps with an option to override). 
I was considering that, but the Racket conventions for function names
(lower-case, using hyphens) are not valid Neovim function names. The
user would either have to use un-Racketey names in Racket code or
specify the name any time, so I might as well make the name mandatory.


> here is how I might write this:

Let me see if I understand how your macro works.

  (define-splicing-syntax-class options
  #:attributes (name [opt 1])
  (pattern (~seq (~alt (~once (~seq #:name   name:expr))
   (~optional (~seq #:range range:expr))
   (~optional (~seq #:eval   eval:expr))
   (~optional (~seq #:sync  sync?:expr)))
 ...)
   #:with (opt ...)
   #`(#,@(for*/list ([clause (in-list (list (attr? #:range range)
(attr? #:eval   eval)
(attr? #:sync  sync?)))]
 #:when clause
 [term (in-list clause)])
   term

This is the syntax class of all the keyword-value pairs. My first
mistake was using `~alt`, I should have used `(~seq (~alt ...))` instead
because I accept a sequence of pairs, not just one pair. The syntax
class also matches all the pair at once instead of each class instance
matching only one pair.

The `#:with` directive mathes the `opt ...` pattern against a syntax
object which has been constructed out of all the non-name pairs. But how
does the `name` attribute get its value? Because it's not an `opt`?


  (syntax-parse stx
[(_ raw-lhs:expr opts:options
body:expr ...+)

The `raw-lhs` is the thing we want defined. How does it know to match
both `foo` and `(foo bar baz)`?

  #:do [(define-values (ident-stx rhs-stx)
  (normalize-definition #'(define raw-lhs body ...) #'λ #t #t))]

I don't understand why we need this. It takes apart a definition we
built on the fly, takes it apart, but in the template we put thing back
together in the same order:

  #'(begin 
  (define ident rhs)
  (register-function! ident opts.name opts.opt ...))]))

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


Re: [racket-users] Need help improving a macro

2017-08-30 Thread Philip McGrath
On Wed, Aug 30, 2017 at 11:08 AM,  wrote:

> How did other people learn Racket meta programming?
>

I don't consider myself a huge expert in macros, but personally I found the
"Examples" section of the syntax/parse documentation very helpful (
http://docs.racket-lang.org/syntax/stxparse.html), as well as Greg
Hendershot's "Fear of Macros" (
http://www.greghendershott.com/fear-of-macros/). As with everything else,
though, there's no substitute for just getting experience actually writing
and debugging some macros.


> It looks like your macro is turning strings into symbols.
>

It looks like that because strings `display` without quotation marks (try
`(displayln "I am a string.")`). If you want use `syntax->datum`, try
`println` instead.

(Also, probably obviously, I only defined `register-nvim-function!` as a
macro for illustrative purposes.)

The `#:with` directive mathes the `opt ...` pattern against a syntax
> object which has been constructed out of all the non-name pairs.


Yes. The two tricky things are that I include the keywords themselves as
well as their value expressions for easy destructuring later and that drop
any clause that doesn't appear. Using a `#:defaults` declaration is an
alternative, but personally I've come to the point of view that you should
avoid having the default value expression repeated both in the function and
in the macro.


> But how does the `name` attribute get its value? Because it's not an `opt`?
>
The name attribute gets its value from the `name` pattern variable. The
`#:attributes` declaration is strictly optional, but I like it both for my
own ease of understanding later and because it is checked.


> The `raw-lhs` is the thing we want defined. How does it know to match
> both `foo` and `(foo bar baz)`?
>
> I don't understand why we need this. It takes apart a definition we
> built on the fly, takes it apart, but in the template we put thing back
> together in the same order:
>

Because `raw-lhs` is annotated as an `expr`, it matches anything that isn't
a keyword, including both `name` and `(name arg1 arg2)`, as well as keyword
and optional arguments, for example. Then `normalize-definition` handles
converting the `define`-like form into a plain identifier to be defined and
a single `λ` expression, including checking for other subtle errors like
duplicate argument identifiers. With `syntax-parse` it's much easier to
write those sort of checks yourself than it was with just `syntax-case`,
but I personally still like the library function.

-- 
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.