2

My aim is to populate an array in compile phase (i.e. in a macro), and use it in execution phase. For some reason, though, object returned by a macro is not recognized by Racket as an array. To illustrate the problem, shortest code showing this behaviour:

(require (for-syntax math/array))
(require math/array)

(define-syntax (A stx)
  (datum->syntax stx `(define a ,(array #[#[1 2] #[3 4]]))))

(A)

After execution of this macro, 'a' is something, but I don't know what it is. It is not an array ((array? a) -> #f) nor a string, array-ref is not working on it, obviously, but it prints as: (array #[#[1 2] #[3 4]]). "class-of" from the "swindle" module claims it is "primitive-class:unknown-primitive", for what it's worth.

I have tried outputting a vector instead of an array, but it works as expected, i.e. resulting value is a vector in execution phase.

I have tried using CommonLisp style defmacro from "compatibility" module, thinking that this may have something to do do with datum->syntax transformation, but this changed nothing.

I have tested this on Win7 with Racket 6.5 and 6.7, as well as on Linux with Racket 6.7 - problem persists.

Any ideas?


update

Thanks to great answers and suggestions, I came up with following solution:

(require (for-syntax math/array))
(require math/array)

(define-syntax (my-array stx)
  (syntax-case stx ()
    [(_ id)
     (let
         ([arr (build-array
                #(20 20)
                (lambda (ind)
                  (let
                      ([x (vector-ref ind 1)]
                       [y (vector-ref ind 0)])
                    (list 'some-symbol x y (* x y)))))])
       (with-syntax ([syn-arr (eval (read (open-input-string (string-append "#'" (format "~v" arr)))))])
         #'(define id syn-arr)))]))

(my-array A)

I'm not sure if this is proper Racket (I welcome all suggestions on code improvement) but here is how it works:

Array is built and stored in "arr" variable. It is then printed to string, prepended with #' (so that this string represents syntax object now) and evaluated as code. This effectively converts array to syntax object, that can be embedded in macro output.

Advantage of this approach is, that every object that can be written out and then read back by Racket can be output by macro. Disadvantage is, that some objects can't (I'm looking at you, custom struct!) and therefore additional string-creating function may be required in some cases.

1 Answers1

4

First of all, don’t use datum->syntax like that. You’re throwing away all hygiene information there, so if someone was using a different language where define was called something else (like def, for example), that would not work. For a principled introduction to Racket macros, consider reading Fear of Macros.

Second of all, the issue here is that you are creating what is sometimes known as “3D syntax”. 3D syntax should probably be an error in this context, but the gist is that there is only a small set of things that you can safely put inside of a syntax object:

  • a symbol
  • a number
  • a boolean
  • a character
  • a string
  • the empty list
  • a pair of two pieces of valid syntax
  • a vector of valid syntax
  • a box of valid syntax
  • a hash table of valid syntax keys and values
  • a prefab struct containing exclusively valid syntax

Anything else is “3D syntax”, which is illegal as the output of a macro. Notably, arrays from math/array are not permitted.

This seems like a rather extreme limitation, but the point is that the above list is simply the list of things that can end up in compiled code. Racket does not know how to serialize arbitrary things to bytecode, which is reasonable: it wouldn’t make much sense to embed a closure in compiled code, for example. However, it’s perfectly reasonable to produce an expression that creates an array, which is what you should do here.

Writing your macro more properly, you would get something like this:

#lang racket

(require math/array)

(define-syntax (define-simple-array stx)
  (syntax-case stx ()
    [(_ id)
     #'(define id (array #(#(1 2) #(3 4))))]))

(define-simple-array x)

Now, x is (array #[#[1 2] #[3 4]]). Note that you can remove the for-syntax import of math/array, since you are no longer using it at compile time, which makes sense: macros just manipulate bits of code. You only need math/array at runtime to create the actual value you end up with.

Community
  • 1
  • 1
Alexis King
  • 43,109
  • 15
  • 131
  • 205
  • 1
    I suspect the OP is hoping that Racket can support array literals such that its use doesn't constitute 3D syntax. I say this because in Guile, regexps aren't simple datums and so you can't use precompiled regexps in Guile macros without creating 3D syntax, whereas Racket does support regexp literals directly in macros without 3D syntax. – C. K. Young Dec 14 '16 at 07:27
  • 2
    @ChrisJester-Young Yeah. Extensible literals is currently an open problem in Racket (and one many people, including myself, are interested in, since Racket allows extending the language in so many ways). Current proposed solutions are relatively unsatisfying—they mostly involve eschewing `quote` entirely or making `quote` far more complicated than it currently is, ruining the whole simplicity of `quote`—but it’s unclear if that can be solved in a general way without solving a lot of tricky problems and doing a lot of implementation work. – Alexis King Dec 14 '16 at 07:29
  • Great answer, thanks! I have only one small problem with your solution. I'm aware, that I can cede computation of an array to execution phase. In simplified example this is perfectly acceptable and sensible. What I tried to do, was to move array computation to a macro. In other words, I know that inside a macro I can do (hope this is eligible): `(define a (build-array #(20 20) (lambda (ind) ))) but without a comma before "build-array" it defeats the purpose. I will have to go with vector of vectors then(?) – Lech Napierała Dec 14 '16 at 08:13
  • 1
    @LechNapierała You could write a function that converts an array into a syntax object representing an expression that produces that same array. That is, you could write a function that turns `(array #[1 2])` into `#'(array #[1 2])`. You couldn’t do it for all potential values inside the array, but for simple things like numbers, it would be relatively straightforward. – Alexis King Dec 14 '16 at 08:29
  • @Alexis King - and that's what I'm going to try next : ) – Lech Napierała Dec 14 '16 at 08:53
  • 1
    @LechNapierała If you can simply, use vectors instead of arrays. – soegaard Dec 14 '16 at 19:22
  • > *someone was using a different language where define was called something else ...* That someone is busy being flogged with a wet towel. – Kaz Dec 15 '16 at 15:17
  • @Kaz It’s not nearly as unreasonable as you might think… [Jay McCarthy’s own proposal for a `#lang racket` successor, `#lang remix`](https://github.com/jeapostrophe/remix), uses `def`. – Alexis King Dec 15 '16 at 22:17