Yeah, if I have some time I’ll see if I can put something together.
Hello everyone!)
Is it possible to disable hygiene in a macro definition? Everything looks like okay, expands okay but when I try to evaluate the entire module I get an error: “unbound identifier in module”.
All the day I struggle and suffer. Using Common Lisp or Clojure I’d solve it long time ago. (
@dmitryhertz You can use raco macro-stepper file.rkt and press the End >>\| button to get to your error message, then press \|< Step to go back step by step until your error disappears. This will show you the current state of the macro expander, allowing you to see what’s going wrong with your macro.
It’s a bit hard to read at first, but you’ll soon get the grasp of it
As for hygiene, it’s built-in in Racket and allows a very powerful (alas complex at first) macro system. But they are ways to break hygiene. If you want to inject identifiers in scope, for example (thus breaking lexical scope) the prefered way is to use syntax-parameterize
I mean, there exists defmacro but it’s a footgun and kittens will die if you use it https://docs.racket-lang.org/compatibility/defmacro.html
A simple example is the aif macro (anaphoric if): https://github.com/jsmaniac/anaphoric/blob/master/aif.rkt
It’s better to follow @jerome.martin.dev’s advice and try to figure it out for real
Okay. I’ll try syntax-parameterize, @jerome.martin.dev thank you!
@greg you made the great article! I mean “the fear of macros”. Thank you! :slightly_smiling_face:
Yes, greg’s article is very helpful :smile:
@dmitryhertz btw I think there’s an issue in your code, as you are using an unquote instead of a syntax-unquote here: #`(define (fn-id ld attr ...) ,req resp) I think it should be #,req, but maybe I’m wrong.
Or maybe actually no unquoting at all, since req is a syntax variable.
@jerome.martin.dev I already removed , from ,req, I added the comma there accidentally. Anyway, the error is: ; rdn: unbound identifier in module
; context...:
; #(2290265 module) #(2290266 module ldap 0) #(2292600 macro) #(2292909 local)
; #(2292910 intdef) #(2292913 local) #(2292914 intdef) #(2292917 local)
; #(2292918 intdef) #(2292921 local) #(2292922 intdef) #(2292925 local)
; #(2292926 intdef)
“unbound identifier in module” can happen when your macro supplies some identifier, but you didn’t require the module that provides it, where you defined the macro.
So e.g. this will give that error: #lang racket/base
(module m racket/base
(require (for-syntax racket/base
syntax/parse))
(define-syntax (s stx)
(syntax-parse stx
[_ #'get-pure-port]))
(provide s))
(require 'm)
s
But this will work: #lang racket/base
(module m racket/base
(require (for-syntax racket/base
syntax/parse))
(require net/url) ;; <============== NEW ========
(define-syntax (s stx)
(syntax-parse stx
[_ #'get-pure-port]))
(provide s))
(require 'm)
s
TL;DR: If your macro is supplying things like write-asn1/DER then in your macro-defining file be sure to require the modules that provide it
Not sure that’s the problem here, but it’s a mistake you can make
in his case I think rdn stands for an attribute passed when using the macro, so I guess the macro actually compiles.
Hmm… I realized that I cannot pass attr … to syntax-parameterize
The problem is in attr … in that it can have different length in various the macro calls.
yes, you can use syntax-class to simplify your macro. Wait a bit, I’m putting something together for you.
the issue is that you’re putting parts of the syntax in a hash to then build up the result. But using syntax-class, you can remove the hash stuff and use req.asn1-type, req.app ..etc
> you’re putting parts of the syntax in a hash It seemed to me that it’s the wrong technique, now I used it to be able to put keys and values in random order. Okay, I’ll try to rewrite it withous this hash. :slightly_smiling_face:
This is a beginning (define-syntax (define-ldap stx)
(define-splicing-syntax-class type
(pattern (~seq #:asn1-type type:id))
(pattern (~seq) #:with type #'SomeDefaultValue))
(define-splicing-syntax-class req-expr
(pattern (~seq t:type a:app)))
(syntax-parse stx
[(_ (name:id ld attr ...)
[request re:req-expr ...]
[response resp-expr]) ;; ...rest of the macro
In short, you can declare every possible part of the request syntax, and put them together
here I declare a type class which must be a sequence of two elements, #:asn1-type followed by an identifier called type
it can also be an empty sequence (~seq), in which case type will be equal to SomeDefaultValue
then I declare a req-expr class to be able to contain a sequence of a type class and an app class (not shown here)
the code is not complete, and I gotta go, but you can check out the (~seq) documentation https://docs.racket-lang.org/syntax/stxparse-patterns.html?q=syntax%2Dparse#%28form._%28%28lib._syntax%2Fparse..rkt%29._~7eseq%29%29
to use a class, just add it after a colon : on your macro identifiers
for example (_ name:id age:int photo:my-photo-class)
then you can use the class in your template with a dot notation: #'(define (student)
(let ([my-name name]
[my-age age])
(display photo.description)
(display photo.image)))
I tried to use define-splicing-syntax-class when I read asn1 library sources https://github.com/rmculpepper/asn1/blob/master/asn1-lib/main.rkt So, probably it’s worth to try to use it again. )
my syntax-parse knowledge is fairly new, other people will surely correct me :wink: You need to find a (pattern) that allows a sequence of request “parts”. I don’t know if you need them in the right order or not.
@jerome.martin.dev it nearly works!:) I’m about completing it, I hope soon it will be working fine
Great :smile: I think you don’t need those extra syntax-parse though
@jerome.martin.dev @greg thank you very much!:+1:
You’re welcome :slightly_smiling_face:
So, now I use #,(stx-cadr #’req.n) instead of #,(syntax-parse #’req.n [(k:keyword n) #’n]) form. Maybe there’s something better, maybe I’ll fix it in the near future.
Oh, it’s great to be able to develop some macros in Racket.
I discovered syntax-parse and syntax-class some time ago. I can’t go back, it’s too good :smile:
Yeah I don’t know yet how to do stuff like #'(hello my.super.deep.nested.value)
I guess it’s not possible yet
That would be cool though
If I understood it right, @greg wrote about it here at 4.3 section: http://www.greghendershott.com/fear-of-macros/all.html
I guess it’s possible to develop something like this
But probably it won’t be easy for a racketeer who doesn’t have enough experience with Racket’s metaprogramming.
For example, I spent 2–3 days to write define-ldap macro and I’d be struggling 2–3 days more without community’s great help
Yes, the first time you try something with macros in Racket, it can be a real struggle. But when the frustration passes by, you realize you learned something valuable for the rest of your life x)
needs to update FoM to add a link to http://docs.racket-lang.org/syntax-parse-example/index.html
Sometime after I wrote that infix dot example, Racket added a reader option to produce #%dot forms. Which might be a better way to do this if you control the reader, e.g. for your own #lang. (But I guess maybe not for a generic macro?) http://docs.racket-lang.org/reference/reader.html?q=dot%20reader#%28part._parse-cdot%29
@mflatt If I eval a require statement for a relative module path, how would I tell Racket to require the file relative to a different directory.
By setting current-load-relative-directory, or by building up a more absolute module path with operations like module-path-index-join
Okay thanks. I also see current-load, and current-module-name-resolver, where do they fit in the picture?
It almost looks to me like current-load and current-module-name-resolver both use the current-load-relative-directory parameter, but I’m not sure how those two interplay, as the docs seem to imply they are both used for require forms.
@mflatt: I’m trying to construct and evaluate a module with syntax I load from a file, such that relative requires in that syntax will be relative to the file I loaded it from. I can’t just load from a path because I want to control the #lang independently from the file.
(This is for a test runner where I have several equivalent implementations of a language and want to run the same test file with each implementation)
Whoops: nevermind. Just realized the code I pasted didn’t have the current-load-relative-directory parameterize around the require that finally triggers evaluation.
With that it’s fixed.
Though if there’s a better way to do this, I’d be interested.
@mflatt @michael.ballantyne Is that actually right though?
Like, if I require a.rkt, which in tern requires b.rkt, I would expect b.rkt to be required relative to a.rkt, not the file that required a.rkt.
Oh interesting, if I just switch the (eval '(require 'a)) to (require "Desktop/a.rkt"), and then print out the load paths with:
(begin-for-syntax
(define prev-load (current-load))
(current-load
(λ args
(displayln args)
(apply prev-load args))))it does update the path properly.
Ah, okay. So if I make another c.rkt file that b.rkt requires, then you do get the right path. It seems like its just because the initial require is in an eval.