unofficial mirror of help-guix@gnu.org 
 help / color / mirror / Atom feed
* boiler plate generation with hygenic macros in guile
@ 2022-07-20 23:36 Blake Shaw
  2022-07-20 23:57 ` Blake Shaw
  0 siblings, 1 reply; 4+ messages in thread
From: Blake Shaw @ 2022-07-20 23:36 UTC (permalink / raw)
  To: help-guix

Hi folks,

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: boiler plate generation with hygenic macros in guile
  2022-07-20 23:36 boiler plate generation with hygenic macros in guile Blake Shaw
@ 2022-07-20 23:57 ` Blake Shaw
  2022-07-25 12:35   ` Liliana Marie Prikler
  0 siblings, 1 reply; 4+ messages in thread
From: Blake Shaw @ 2022-07-20 23:57 UTC (permalink / raw)
  To: help-guix

Ah! sorry, let me begin again:

Right now I am working on a window manager extension system in Guile and
GOOPs, and I want to eliminate the boilerplate for generating class slots,
with a syntax-case macro like:

#+begin_example scheme
(define-syntax slot-machine
    (λ (form)
      (syntax-case form ()
    ((_ category quality value)
       #'(let* ((sym      (symbol-append category '- quality))
        (set-sym! (symbol-append 'set- sym '!))
        (get-sym  (symbol-append 'get- sym))
        (acc-sym  (symbol-append 'acc- sym)))
        (if (or (symbol? value) (string? value) (number? value))
        (quasiquote (,sym #:init-value value
                  #:setter       ,set-sym!
                  #:getter       ,get-sym
                  #:accessor   ,acc-sym))
        (quasiquote (,sym #:init-form  value
                  #:setter       ,set-sym!
                  #:getter       ,get-sym
                  #:accessor   ,acc-sym))))))))
#+end_example

With this I can call (slot-machine 'inner 'color "green") to produce what
looks like an acceptable slot definition:
=> (inner-color #:init-value "#BF3D52" #:setter set-inner-color! #:getter
get-inner-color #:accessor acc-inner-color)

Indeed, if I define a class with this slot definition in place, it works
fine:

#+begin_example scheme

(define-class <dummy> ()
    (inner-color #:init-value "#BF3D52" #:setter set-inner-color! #:getter
get-inner-color #:accessor acc-inner-color))

(describe <dummy>)
=> <dummy> is a class. It's an instance of <class>
Superclasses are:
    <object>
Directs slots are:
    inner-color
(No direct subclass)
Class Precedence List is:
    <dummy>
    <object>
    <top>
Class direct methods are:
    Method #<<accessor-method> (<dummy> <top>) 7f7b27e10ac0>
         Generic: setter:acc-inner-color
    Specializers: <dummy> <top>
    Method #<<accessor-method> (<dummy>) 7f7b27e10b00>
         Generic: acc-inner-color
    Specializers: <dummy>
    Method #<<accessor-method> (<dummy> <top>) 7f7b27e10b40>
         Generic: set-inner-color!
    Specializers: <dummy> <top>
    Method #<<accessor-method> (<dummy>) 7f7b27e10b80>
         Generic: get-inner-color
    Specializers: <dummy>

#+end_example

But if I try to use `slot-machine` inside a class definition i'm out of
luck:

(define-class <dummy> ()
  (slot-machine 'inner 'color "green"))
=> While compiling expression:
Syntax error:
socket:7257:0: source expression failed to match any pattern in form
(define-class-pre-definition ((quote inner) (quote color) "green"))

I have tried to remedy this ina number of ways, using datum->syntax,
quasisyntax/unsyntax, make-variable and by defining a new syntax-case macro
to define classes, all without luck.

This is actually a recurring theme with my experience with Guile, working
on a project, needing to generate boilerplate, and then being unable to
find a result, so I figured its time I reach out to figure out what I'm
doing wrong in this situation.

Thanks for your help!

Best,
Blake

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: boiler plate generation with hygenic macros in guile
  2022-07-20 23:57 ` Blake Shaw
@ 2022-07-25 12:35   ` Liliana Marie Prikler
  2022-07-28 20:59     ` Blake Shaw
  0 siblings, 1 reply; 4+ messages in thread
From: Liliana Marie Prikler @ 2022-07-25 12:35 UTC (permalink / raw)
  To: Blake Shaw, help-guix

Hi Blake,

Am Mittwoch, dem 20.07.2022 um 23:57 +0000 schrieb Blake Shaw:
> Ah! sorry, let me begin again:
> 
> Right now I am working on a window manager extension system in Guile
> and GOOPs, and I want to eliminate the boilerplate for generating class
> slots, with a syntax-case macro like:
> 
> #+begin_example scheme
> (define-syntax slot-machine
>     (λ (form)
>       (syntax-case form ()
>     ((_ category quality value)
>        #'(let* ((sym      (symbol-append category '- quality))
>         (set-sym! (symbol-append 'set- sym '!))
>         (get-sym  (symbol-append 'get- sym))
>         (acc-sym  (symbol-append 'acc- sym)))
>         (if (or (symbol? value) (string? value) (number? value))
>         (quasiquote (,sym #:init-value value
>                   #:setter       ,set-sym!
>                   #:getter       ,get-sym
>                   #:accessor   ,acc-sym))
>         (quasiquote (,sym #:init-form  value
>                   #:setter       ,set-sym!
>                   #:getter       ,get-sym
>                   #:accessor   ,acc-sym))))))))
> #+end_example
Now, first of all, I'd advise you to use (macroexpand '(expression))
whenever debugging your syntax-rules or syntax-cases.  In this example,
> (macroexpand (slot-machine 'inner 'quality #t))
yields
> #<tree-il (call (toplevel inner-quality) (const #:init-form) (const #t)
> (const #:setter) (toplevel set-inner-quality!) (const #:getter)
> (toplevel get-inner-quality) (const #:accessor) (toplevel acc-inner-
> quality))>
but with an additional quote that's
> <tree-il (let (sym) (sym-1dff1b83541ce327-b4) ((call (toplevel symbol-
> append) (const inner) (const -) (const quality))) (let (set-sym!) (set-
> sym!-1dff1b83541ce327-b6) ((call (toplevel symbol-append) (const set-)
> (lexical sym sym-1dff1b83541ce327-b4) (const !))) (let (get-sym) (get-
> sym-1dff1b83541ce327-b8) ((call (toplevel symbol-append) (const get-)
> (lexical sym sym-1dff1b83541ce327-b4))) (let (acc-sym) (acc-sym-
> 1dff1b83541ce327-ba) ((call (toplevel symbol-append) (const acc-)
> (lexical sym sym-1dff1b83541ce327-b4))) (if (let (t) (t-
> 1dff1b83541ce327-bd) ((call (toplevel symbol?) (const #t))) (if
> (lexical t t-1dff1b83541ce327-bd) (lexical t t-1dff1b83541ce327-bd)
> (let (t) (t-1dff1b83541ce327-c0) ((call (toplevel string?) (const #t)))
> (if (lexical t t-1dff1b83541ce327-c0) (lexical t t-1dff1b83541ce327-c0)
> (call (toplevel number?) (const #t)))))) (call (@@ (guile) list)
> (lexical sym sym-1dff1b83541ce327-b4) (const #:init-value) (const #t)
> (const #:setter) (lexical set-sym! set-sym!-1dff1b83541ce327-b6) (const
> #:getter) (lexical get-sym get-sym-1dff1b83541ce327-b8) (const
> #:accessor) (lexical acc-sym acc-sym-1dff1b83541ce327-ba)) (call (@@
> (guile) list) (lexical sym sym-1dff1b83541ce327-b4) (const #:init-form)
> (const #t) (const #:setter) (lexical set-sym! set-sym!-
> 1dff1b83541ce327-b6) (const #:getter) (lexical get-sym get-sym-
> 1dff1b83541ce327-b8) (const #:accessor) (lexical acc-sym acc-sym-
> 1dff1b83541ce327-ba)))))))>
Ouch.

> With this I can call (slot-machine 'inner 'color "green") to produce
> what looks like an acceptable slot definition:
> => (inner-color #:init-value "#BF3D52" #:setter set-inner-color!
> #:getter get-inner-color #:accessor acc-inner-color)
Note that you're confusing symbols and syntax here.  A common mistake
for the novice macro expander, but in Scheme, symbols and syntax are
distinct.

> Indeed, if I define a class with this slot definition in place, it
> works fine:
> 
> #+begin_example scheme
> 
> (define-class <dummy> ()
>     (inner-color #:init-value "#BF3D52" #:setter set-inner-color!
> #:getter
> get-inner-color #:accessor acc-inner-color))
> 
> (describe <dummy>)
> => <dummy> is a class. It's an instance of <class>
> Superclasses are:
>     <object>
> Directs slots are:
>     inner-color
> (No direct subclass)
> Class Precedence List is:
>     <dummy>
>     <object>
>     <top>
> Class direct methods are:
>     Method #<<accessor-method> (<dummy> <top>) 7f7b27e10ac0>
>          Generic: setter:acc-inner-color
>     Specializers: <dummy> <top>
>     Method #<<accessor-method> (<dummy>) 7f7b27e10b00>
>          Generic: acc-inner-color
>     Specializers: <dummy>
>     Method #<<accessor-method> (<dummy> <top>) 7f7b27e10b40>
>          Generic: set-inner-color!
>     Specializers: <dummy> <top>
>     Method #<<accessor-method> (<dummy>) 7f7b27e10b80>
>          Generic: get-inner-color
>     Specializers: <dummy>
> 
> #+end_example
> 
> But if I try to use `slot-machine` inside a class definition i'm out
> of luck:
> 
> (define-class <dummy> ()
>   (slot-machine 'inner 'color "green"))
> => While compiling expression:
> Syntax error:
> socket:7257:0: source expression failed to match any pattern in form
> (define-class-pre-definition ((quote inner) (quote color) "green"))

Now if we were to try and debug this with our newly found trick, we'd
quickly end up disappoined.  macroexpand can not expand this form for
the same reason as above: the source expression does not match any
pattern in define-class.  But what does this mean?
See, if you write your code as you did above, define-class will look at
the form (slot-machine 'inner 'quality #t) – in its unexpanded form! –
decide, that it can't handle it, and raise the above error.  Instead,
you need a macro which expands to
> (define-class <dummy> () (inner-quality #:init-value #t #:setter
> set-inner-quality! #:getter get-inner-quality #:accessor
> acc-inner-quality)))
which IOW means wrapping define-class in a macro.

As a handy little guide, see the macroexpand blow without define-class
actually defined.  Note the extra quote around (), which you'll have to
remove in your output.
> (macroexpand '(define-class <dummy> '() (inner-quality #:init-value
> #t #:setter set-inner-quality! #:getter get-inner-quality #:accessor
> acc-inner-quality)))
> $1 = #<tree-il (call (toplevel define-class) (toplevel <dummy>)
> (const ()) (call (toplevel inner-quality) (const #:init-value) (const
> #t) (const #:setter) (toplevel set-inner-quality!) (const #:getter)
> (toplevel get-inner-quality) (const #:accessor) (toplevel
> acc-inner-quality)))>

> I have tried to remedy this in a number of ways, using datum->syntax,
> quasisyntax/unsyntax, make-variable and by defining a new syntax-case
> macro to define classes, all without luck.
So we now arrive at the conclusion that only a syntax-rules/syntax-case
wrapper around define-class can remedy this, but you're still tasked
with the actual implementation.  Note, that the symbol-append of 'inner
'- 'quality actually gets you nothing and only restricts you.  I'd also
advise you to keep compatibility with define-class as much as possible,
that will significantly lessen your work.  Then you "only" need to add
your own special expansion rules with extra literals (literals go
between the first pairs of parentheses in syntax-rules/syntax-case).  


Cheers


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: boiler plate generation with hygenic macros in guile
  2022-07-25 12:35   ` Liliana Marie Prikler
@ 2022-07-28 20:59     ` Blake Shaw
  0 siblings, 0 replies; 4+ messages in thread
From: Blake Shaw @ 2022-07-28 20:59 UTC (permalink / raw)
  To: Liliana Marie Prikler; +Cc: help-guix, Maxime Devos

Hi Maxime & Liliana,

I just want to pop in and say thanks for your advice, its been helpful. I
almost have the implementation down, but a few tricky bits I still havent
got right... (what I'm attempting is a little more tricky than the example
I gave here, which I shortened for the sake of transmissibility -- the goal
is to be able to generate classes from dictionaries in an ad-hoc manner).

Feel like I'm almost there, just a few bits to work out, but I won't be
able to return to it for another week or so. As soon as I have it down I'll
report back in detail in case it could help others.

Happy hacking!
b


On Mon, Jul 25, 2022 at 12:35 PM Liliana Marie Prikler <
liliana.prikler@ist.tugraz.at> wrote:
>
> Hi Blake,
>
> Am Mittwoch, dem 20.07.2022 um 23:57 +0000 schrieb Blake Shaw:
> > Ah! sorry, let me begin again:
> >
> > Right now I am working on a window manager extension system in Guile
> > and GOOPs, and I want to eliminate the boilerplate for generating class
> > slots, with a syntax-case macro like:
> >
> > #+begin_example scheme
> > (define-syntax slot-machine
> >     (λ (form)
> >       (syntax-case form ()
> >     ((_ category quality value)
> >        #'(let* ((sym      (symbol-append category '- quality))
> >         (set-sym! (symbol-append 'set- sym '!))
> >         (get-sym  (symbol-append 'get- sym))
> >         (acc-sym  (symbol-append 'acc- sym)))
> >         (if (or (symbol? value) (string? value) (number? value))
> >         (quasiquote (,sym #:init-value value
> >                   #:setter       ,set-sym!
> >                   #:getter       ,get-sym
> >                   #:accessor   ,acc-sym))
> >         (quasiquote (,sym #:init-form  value
> >                   #:setter       ,set-sym!
> >                   #:getter       ,get-sym
> >                   #:accessor   ,acc-sym))))))))
> > #+end_example

> whenever debugging your syntax-rules or syntax-cases.  In this example,
> > (macroexpand (slot-machine 'inner 'quality #t))
> yields
> > #<tree-il (call (toplevel inner-quality) (const #:init-form) (const #t)
> > (const #:setter) (toplevel set-inner-quality!) (const #:getter)
> > (toplevel get-inner-quality) (const #:accessor) (toplevel acc-inner-
> > quality))>
> but with an additional quote that's
> > <tree-il (let (sym) (sym-1dff1b83541ce327-b4) ((call (toplevel symbol-
> > append) (const inner) (const -) (const quality))) (let (set-sym!) (set-
> > sym!-1dff1b83541ce327-b6) ((call (toplevel symbol-append) (const set-)
> > (lexical sym sym-1dff1b83541ce327-b4) (const !))) (let (get-sym) (get-
> > sym-1dff1b83541ce327-b8) ((call (toplevel symbol-append) (const get-)
> > (lexical sym sym-1dff1b83541ce327-b4))) (let (acc-sym) (acc-sym-
> > 1dff1b83541ce327-ba) ((call (toplevel symbol-append) (const acc-)
> > (lexical sym sym-1dff1b83541ce327-b4))) (if (let (t) (t-
> > 1dff1b83541ce327-bd) ((call (toplevel symbol?) (const #t))) (if
> > (lexical t t-1dff1b83541ce327-bd) (lexical t t-1dff1b83541ce327-bd)
> > (let (t) (t-1dff1b83541ce327-c0) ((call (toplevel string?) (const #t)))
> > (if (lexical t t-1dff1b83541ce327-c0) (lexical t t-1dff1b83541ce327-c0)
> > (call (toplevel number?) (const #t)))))) (call (@@ (guile) list)
> > (lexical sym sym-1dff1b83541ce327-b4) (const #:init-value) (const #t)
> > (const #:setter) (lexical set-sym! set-sym!-1dff1b83541ce327-b6) (const
> > #:getter) (lexical get-sym get-sym-1dff1b83541ce327-b8) (const
> > #:accessor) (lexical acc-sym acc-sym-1dff1b83541ce327-ba)) (call (@@
> > (guile) list) (lexical sym sym-1dff1b83541ce327-b4) (const #:init-form)
> > (const #t) (const #:setter) (lexical set-sym! set-sym!-
> > 1dff1b83541ce327-b6) (const #:getter) (lexical get-sym get-sym-
> > 1dff1b83541ce327-b8) (const #:accessor) (lexical acc-sym acc-sym-
> > 1dff1b83541ce327-ba)))))))>
> Ouch.
>
> > With this I can call (slot-machine 'inner 'color "green") to produce
> > what looks like an acceptable slot definition:
> > => (inner-color #:init-value "#BF3D52" #:setter set-inner-color!
> > #:getter get-inner-color #:accessor acc-inner-color)
> Note that you're confusing symbols and syntax here.  A common mistake
> for the novice macro expander, but in Scheme, symbols and syntax are
> distinct.
>
> > Indeed, if I define a class with this slot definition in place, it
> > works fine:
> >
> > #+begin_example scheme
> >
> > (define-class <dummy> ()
> >     (inner-color #:init-value "#BF3D52" #:setter set-inner-color!
> > #:getter
> > get-inner-color #:accessor acc-inner-color))
> >
> > (describe <dummy>)
> > => <dummy> is a class. It's an instance of <class>
> > Superclasses are:
> >     <object>
> > Directs slots are:
> >     inner-color
> > (No direct subclass)
> > Class Precedence List is:
> >     <dummy>
> >     <object>
> >     <top>
> > Class direct methods are:
> >     Method #<<accessor-method> (<dummy> <top>) 7f7b27e10ac0>
> >          Generic: setter:acc-inner-color
> >     Specializers: <dummy> <top>
> >     Method #<<accessor-method> (<dummy>) 7f7b27e10b00>
> >          Generic: acc-inner-color
> >     Specializers: <dummy>
> >     Method #<<accessor-method> (<dummy> <top>) 7f7b27e10b40>
> >          Generic: set-inner-color!
> >     Specializers: <dummy> <top>
> >     Method #<<accessor-method> (<dummy>) 7f7b27e10b80>
> >          Generic: get-inner-color
> >     Specializers: <dummy>
> >
> > #+end_example
> >
> > But if I try to use `slot-machine` inside a class definition i'm out
> > of luck:
> >
> > (define-class <dummy> ()
> >   (slot-machine 'inner 'color "green"))
> > => While compiling expression:
> > Syntax error:
> > socket:7257:0: source expression failed to match any pattern in form
> > (define-class-pre-definition ((quote inner) (quote color) "green"))
>
> Now if we were to try and debug this with our newly found trick, we'd
> quickly end up disappoined.  macroexpand can not expand this form for
> the same reason as above: the source expression does not match any
> pattern in define-class.  But what does this mean?
> See, if you write your code as you did above, define-class will look at
> the form (slot-machine 'inner 'quality #t) – in its unexpanded form! –
> decide, that it can't handle it, and raise the above error.  Instead,
> you need a macro which expands to
> > (define-class <dummy> () (inner-quality #:init-value #t #:setter
> > set-inner-quality! #:getter get-inner-quality #:accessor
> > acc-inner-quality)))
> which IOW means wrapping define-class in a macro.
>
> As a handy little guide, see the macroexpand blow without define-class
> actually defined.  Note the extra quote around (), which you'll have to
> remove in your output.
> > (macroexpand '(define-class <dummy> '() (inner-quality #:init-value
> > #t #:setter set-inner-quality! #:getter get-inner-quality #:accessor
> > acc-inner-quality)))
> > $1 = #<tree-il (call (toplevel define-class) (toplevel <dummy>)
> > (const ()) (call (toplevel inner-quality) (const #:init-value) (const
> > #t) (const #:setter) (toplevel set-inner-quality!) (const #:getter)
> > (toplevel get-inner-quality) (const #:accessor) (toplevel
> > acc-inner-quality)))>
>
> > I have tried to remedy this in a number of ways, using datum->syntax,
> > quasisyntax/unsyntax, make-variable and by defining a new syntax-case
> > macro to define classes, all without luck.
> So we now arrive at the conclusion that only a syntax-rules/syntax-case
> wrapper around define-class can remedy this, but you're still tasked
> with the actual implementation.  Note, that the symbol-append of 'inner
> '- 'quality actually gets you nothing and only restricts you.  I'd also
> advise you to keep compatibility with define-class as much as possible,
> that will significantly lessen your work.  Then you "only" need to add
> your own special expansion rules with extra literals (literals go
> between the first pairs of parentheses in syntax-rules/syntax-case).
>
>
> Cheers

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2022-07-28 20:59 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-07-20 23:36 boiler plate generation with hygenic macros in guile Blake Shaw
2022-07-20 23:57 ` Blake Shaw
2022-07-25 12:35   ` Liliana Marie Prikler
2022-07-28 20:59     ` Blake Shaw

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).