I did this:
#lang racket
(define-syntax-rule (macro-expansion-seconds)
(current-seconds))
which does this
> (macro-expansion-seconds)
1639244531
> (macro-expansion-seconds)
1639244532
> (macro-expansion-seconds)
1639244533
It replaces (macro-expandsion-seconds) with (current-seconds) at expansion time, and still evaluates that form each time.
But how do I make it evaluate (current-seconds) at expansion time, and then do this?
> (macro-expansion-seconds)
1639244533
> (macro-expansion-seconds)
1639244533
> (macro-expansion-seconds)
1639244533
I know in this case I could just do this
(define macro-expansion-seconds
(current-seconds))
and this
> macro-expansion-seconds
1639244766
> macro-expansion-seconds
1639244766
> macro-expansion-seconds
1639244766
(name no longer makes sense)
but I am trying to figure out how macros work, and thought this would be an easy example that could help me figure out harder ones.
The simplest way would be:
#lang racket
(begin-for-syntax
(define the-time (current-seconds)))
(define-syntax (macro-expansion-seconds stx)
(datum->syntax stx the-time))
(macro-expansion-seconds)
(macro-expansion-seconds)
(macro-expansion-seconds)
Here begin-for-syntax tells the expander that the following expressions are to be evaluated at compile time. Thus the value of the-time will be a number. The macro then needs to turn the number (which is a datum) into a piece of syntax. That's the job of datum->syntax.
Related
I got this answer https://stackoverflow.com/a/70318991 about writing a simple macro that records the time at macro expansion time, and then always returns that time.
#lang racket
(begin-for-syntax
(define the-time (current-seconds)))
(define-syntax (macro-expansion-seconds stx)
(datum->syntax stx the-time))
(macro-expansion-seconds)
(macro-expansion-seconds)
(macro-expansion-seconds)
It works great, but now is there an easy way to see an expanded version of (macro-expansion-seconds) without evaluating it? (for debugging more complicated ones)
You can use
(expand #'(macro-expansion-seconds))
in the DrRacket repl.
It will show you the graphical representation of a syntax object - remember to click the little arrow! In Mythical Macros I have written a little syntax objects.
https://soegaard.github.io/mythical-macros/
An alternative is to use the "Macro Stepper". Click the button in upper right corner of DrRacket: the icon is a combination of consists of a # and a play symbol.
As just one of many possible examples, break-example.rkt would be a perfectly valid Java program, except for the #lang mini-java header that Racket requires.
So e.g. if I've written a Java interpreter/compiler in Racket as a Racket module language, how can I say, "require this file Main.java which is written in module language mini-java but doesn't have any Racket-specific header"?
(Note that I have almost non-zero practical experience with Racket. I'm evaluating this for a specific use case I have for Racket + DrRacket, which has nothing to do with Java by the way. I searched the documentation but couldn't find any way to achieve this.)
I can’t run or test this right now, but maybe you can start from here and experiment with it. The main thing it uses is include/reader:
#lang racket
(require racket/include
syntax/parse/define
(for-syntax racket/syntax
racket/port
syntax/modread))
(define-simple-macro (require/mini-java path)
#:with modname (generate-temporary #'path)
(begin
(include/reader path (mini-java-reader 'modname))
(require 'modname)))
(begin-for-syntax
;; Symbol -> [Any InputPort -> Syntax]
(define ((mini-java-reader modname) src input)
(cond
[(port-closed? input) eof]
[else
(define stx
(with-module-reading-parameterization
(lambda ()
(read-syntax src
(input-port-append #t
(open-input-string "#lang mini-java\n")
input)))))
(close-input-port input)
(syntax-parse stx
[(module _ l . b)
#`(module #,modname l . b)])])))
My son and I are learning Racket together and are building a very simple text-based adventure for use directly from the REPL. So, for example, the player can type (go 'north) or (take 'apple).
After getting some basic stuff working, my son thought that quoting the noun was a bit of a pain (strangely, the parens don't bother him!), and so we hacked around with macros for a bit and we did get something working but it required an explicit function and a corresponding macro e.g.
(define (do-take item) ...)
(define-syntax (take stx)
(define item (cadr (syntax->datum stx)))
(datum->syntax stx `(do-take ',item)))
I figured we could do better than this, so I read around a bit more and came up with this:
(require (for-syntax racket/syntax))
(define-syntax (define-verb stx)
(syntax-case stx ()
[(_ (verb noun) body-first body-rest ...)
(with-syntax ([verb-fun (format-id stx "do-~a" #'verb)])
#'(begin
(define-syntax-rule (verb noun) (verb-fun 'noun))
(define (verb-fun noun) body-first body-rest ...)))]))
So now, we can write (define-verb (take item) ...) and the player at the REPL can type (take apple).
My question is whether, given what we want to achieve, this is a reasonable approach or whether there a more simple / idiomatic way to achieve the same thing?
In general, the main thing I would recommend doing is using the syntax/parse library. It has more tools for parsing syntax. You can even use forms like define-syntax-parser to make your macro even more concise. Rewriting your code using syntax/parse (dropping that one line because it doesn't seem to be doing anything), your macro would look like this:
#lang racket
(require syntax/parse/define
(for-syntax syntax/parse racket/syntax))
(define-syntax-parser define-verb
[(_ (verb:id noun) body ...+)
(define/syntax-parse verb-fun (format-id stx "do-~a" #'verb))
#'(begin
(define-simple-macro (verb noun) (verb-fun 'noun))
(define (verb-fun noun) body ...))])
This gives you a few nice things above the example you gave:
the :id ensures that verb is a literal identifier, rather than an expression.
the ...+ means you only need to have one body pattern, rather than two.
Using define/syntax-parse means your code does not get more indented than with-syntax. (Although this one is a matter of preference.)
I have the following code that composes a traditional macro eat which uses a quote ('(eating food)), with a make-rename-transformer macro that is supposed to transform lunch into sandwich. The full code is:
#lang racket
(require (for-syntax syntax/parse))
(define-syntax lunch (make-rename-transformer #'sandwich))
(define-syntax (eat stx)
(syntax-parse stx
[(_ food)
#''(eating food)]))
(eat lunch)
Because lunch is just a rename transfomer for sandwich, I would expect it to evaluate to (eat sandwich), and thus '(eating sandwich), but when I run it, I get:
'(eating lunch)
Which is not what I expected. Is there any way I can modify this so that rename transforms in quotes are followed? (As if I had used the list function rather than quote.)
The problem here is just an order of expansion. Unlike functions which, to a first approximation anyway, evaluate inside out. Macros by nature expand outside in, which is an intrinsic property of macro expansion in languages like Racket.
So, the problem is that the first step of macro expa
#lang racket
(define-syntax lunch (make-rename-transformer #'sandwich))
'(eating lunch)
At this point lunch is no longer an identifier, but just a datum, so the rename transformer won't apply here.
What you need to do is tell the macro expander to expand food before you place it in the quote and turn it into datum. Racket's macro expander contains this functionality, [local-expand], if you combine this with #:when (syntax/parse's version of with-syntax), you can get the macro to evaluate food first.
Putting it all together, your code looks like:
#lang racket
(require (for-syntax syntax/parse))
(define-syntax lunch (make-rename-transformer #'sandwich))
(define-syntax (eat stx)
(syntax-parse stx
[(_ food)
#:with expanded-food (local-expand #'food 'expression #f)
#''(eating expanded-food)]))
(eat lunch)
And when you run this, you get:
'(eating sandwich)
I want to turn debugging on and off in my code, so I'm trying to do something like this at the top of my module...
(define DEBUGGING #f)
(if DEBUGGING
(require unstable/debug)
(define (debug x) void))
However I can't require or define inside a conditional. I've had a quick look at dynamic-require but can't figure out how to solve the problem.
First define DEBUGGING as a variable to be used at expansion time, not run time:
(define-for-syntax DEBUGGING #f)
then create a macro such as
(define-syntax (if-debug stx)
(let ((dat (syntax->datum stx)))
(if DEBUGGING
(datum->syntax stx (cadr dat))
(datum->syntax stx (caddr dat)))))
and finally use as
(if-debug
(require unstable/debug)
(define (debug x) void))
uselpa's answer has the correct idea. Here's an alternative implementation of the same idea using syntax-case, which is (IMHO) more readable, with slightly better error checking (if extraneous subforms are passed, for example):
(define-syntax (if-debug stx)
(syntax-case stx ()
((_ debug-expr non-debug-expr)
(if DEBUGGING
#'debug-expr
#'non-debug-expr))))
I like uselpa's answer, especially with Chris' refinement. I've used that sort of approach myself. But honestly: What I would do these days is simply supply both expressions and un-comment the alternative I want at the time:
;; (require unstable/debug)
(define (debug x) (void))
or:
(require unstable/debug)
;; (define (debug x) (void))
Admittedly this "macro via commenting" isn't a runtime choice -- but neither are the macro answers.
Although low tech, this does have the advantage of being just 2 lines of clear, obvious code.
I'd particularly prefer this if it's going to get copied into multiple sources files.
I'd probably prefer this even if it goes in one file that provides debug and is required by many others.
Thanks everyone, I used the answers successfully in a simple script and would like to share the addition. You can actually use any form instead of a boolean DEBUG, effectively not needing to change the code. I didn't want to change/comment-out multiple lines on multiple scripts everytime, and this solution lets me run on drracket and use repl or run from shell (by adding a first arg to the command or not):
; #echo off
; "C:\Program Files\Racket\racket.exe" "%~f0" %*
; exit /b
#lang racket
(define-for-syntax args (current-command-line-arguments))
(define-for-syntax PROTOTYPING (or
(= (vector-length args) 0)
(equal? (vector-ref args 0) "local")))
(define-syntax (if-prototyping stx)
(syntax-case stx ()
((_ debug-expr non-debug-expr)
(if PROTOTYPING
#'debug-expr
#'non-debug-expr))))
(if-prototyping
(require (file "C:/file1.rkt"))
(file "C:/util1.rkt""))
(require (file "C:/file2.rkt"))
(file "C:/util2.rkt")))