Wednesday, October 15, 2014

Gotcha of the Day: Creating macros from within Gambit Scheme on Android

One of the most powerful features of Scheme is the ability to create macros. That is, along with the procedural abstractions most languages allow you to create, you can also create syntactical ones (I've got at least one blog post worth of stuff to say about this, so I dare you to ask me more!). While working on a Scheme Challenge on my cell phone, I wanted a quick way to repeat some code. Specifically, something along the lines of:

 (repeat 10
   (display (read-token in)))

(That is, I wanted to read and print the next 10 tokens from the input port in)

This is an easy enough macro to write in syntax-rules so I got to work. I quickly ran into an issue: define-syntax wasn't defined in the Gambit instance I was using. I tried a few other guesses and finally gave up, figuring I'd poke around the manual when I had a chance.

Sure enough, the manual had an explanation: define-macro is available by default, whereas define-syntax requires an add on module. While I'm no fan of define-macro, for my purposes it would work fine. I went ahead and put the following in ex1.scm, the file containing my answer to the exercise I was working on:

(define (range low high)
  (let loop ((i low) (result '()))
    (if (> i high)
        (reverse result)
        (loop (+ 1 i) (cons i result)))))
        
(define-macro (repeat qty . body)
  `(for-each (lambda (i) ,@body) (range 1 ,qty)))

I then ran my (lex) procedure from the REPL. To my surprise, the file loaded.

I then went to test my code:

(repeat 10 (display i) (newline))

To which the interpreter rudely responded:

*** ERROR IN (stdin)@2.2 -- Unbound variable: repeat

What the heck?

After a couple of more attempts I realized that I could use repeat within ex1.scm, but any attempt to use it from the REPL resulted in an unbound variable. When in doubt, read (or in my case, re-re-read) the manual. Which explains:

The scope of a local macro definition extends from the definition to the end of the body of the surrounding binding construct. Macros defined at the top level of a Scheme module are only visible in that module. To have access to the macro definitions contained in a file, that file must be included using the include special form. Macros which are visible from the REPL are also visible during the compilation of Scheme source files.

(Emphasis added by me)

I updated my (lex) procedure to be defined as:

 (define (lex)
  (include "/sdcard/Documents/ex1.scm"))

And what do you know, it worked! The REPL can now see and use the repeat macro.

In the future, I may mess around with loading the syntax-case facility into Gambit. This actually doesn't look like a particularly hard thing to do, I just know that when I attempted to load this file my phone churned on it for quite a while before returning an error. That makes me think this may be asking for a bit much from my Android. Regardless, define-macro gives me plenty of power, and I'm using it on a small enough scale that its unhygienic nature shouldn't do too much damage (famous last words, right?).

Finally, two other useful bits of info if you're playing around with the Gambit REPL on Android:

1) If you hit run next to the "Start REPL Server" example you can use a program like netcat to connect to the REPL for your laptop. This let me fiddle around with Gambit on my phone while using my laptop's keyboard and display. This is a classic trick, but one that never ceases to amaze me when I use it.

2) If you type ,? at the REPL prompt you'll gain access to a number of debugging commands. I haven't quite figured out the various "REPL levels" yet (that is, what's meant when you have an error and Gambit changes the prompt to 2>). But I now know I can jump to the top level by typing ,t.

2 comments:

  1. Please tell us more about macros!

    ReplyDelete
  2. Ask and you shall receive: http://www.blogbyben.com/2014/10/why-macros-matter.html

    Thanks for asking! ;-)

    ReplyDelete