【问题标题】:Racket: Macro outputs something weird instead of an array球拍:宏输出一些奇怪的东西而不是数组
【发布时间】:2017-04-29 10:27:45
【问题描述】:

我的目标是在编译阶段(即在宏中)填充一个数组,并在执行阶段使用它。但是,由于某种原因,Racket 无法将宏返回的对象识别为数组。为了说明问题,显示此行为的最短代码:

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

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

(A)

这个宏执行后,'a'是什么东西,但我不知道它是什么东西。它不是一个数组 ((array? a) -> #f) 也不是一个字符串,array-ref 显然无法处理它,但它打印为:(array #[#[1 2] #[3 4]])。 "swindle" 模块中的 "class-of" 声称它是 "primitive-class:unknown-primitive",值得。

我尝试过输出一个向量而不是一个数组,但它按预期工作,即结果值是一个处于执行阶段的向量。

我尝试使用“兼容性”模块中的 CommonLisp 样式 defmacro,认为这可能与 datum->syntax 转换有关,但这并没有改变。

我已经在带有 Racket 6.5 和 6.7 的 Win7 以及带有 Racket 6.7 的 Linux 上对此进行了测试 - 问题仍然存在。

有什么想法吗?


更新

感谢很好的回答和建议,我想出了以下解决方案:

(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)

我不确定这是否合适 Racket(我欢迎所有关于代码改进的建议),但它是这样工作的:

数组被构建并存储在“arr”变量中。然后它被打印到字符串中,前面加上#'(这样这个字符串现在代表语法对象)并作为代码进行评估。这有效地将数组转换为语法对象,可以嵌入到宏输出中。

这种方法的优点是,每个可以被Racket写出然后读回的对象都可以通过宏输出。缺点是,有些对象不能(我在看你,自定义结构!),因此在某些情况下可能需要额外的字符串创建函数。

【问题讨论】:

    标签: arrays macros scheme lisp racket


    【解决方案1】:

    首先,不要这样使用datum->syntax。您将在那里丢弃所有卫生信息,因此如果有人使用另一种语言,其中define 被称为其他名称(例如def),那将不起作用。有关 Racket 宏的原理介绍,请考虑阅读 Fear of Macros

    其次,这里的问题是您正在创建有时称为“3D syntax” 的内容。在这种情况下,3D 语法可能是一个错误,但要点是只有一个 small set of things 可以安全地放在语法对象中:

    • 一个符号
    • 一个数字
    • 布尔值
    • 一个字符
    • 一个字符串
    • 空列表
    • 一对两个有效语法
    • 有效语法向量
    • 一盒有效语法
    • 有效语法键和值的哈希表
    • 包含唯一有效语法的预制结构

    Anything else 是“3D 语法”,作为宏的输出是非法的。值得注意的是,不允许使用来自 math/array 的数组。

    这似乎是一个相当极端的限制,但重点是上面的列表只是可以在编译代码中结束的东西的列表。 Racket 不知道如何将任意事物序列化为字节码,这是合理的:例如,在编译后的代码中嵌入闭包没有多大意义。然而,产生一个创建数组的表达式是完全合理的,这就是你应该在这里做的。

    更正确地编写你的宏,你会得到这样的东西:

    #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)
    

    现在,x(array #[#[1 2] #[3 4]])。请注意,您可以删除 math/arrayfor-syntax 导入,因为您不再在编译时使用它,这是有道理的:宏只是操作代码位。你只需要在运行时math/array 来创建你最终得到的实际值。

    【讨论】:

    • 我怀疑 OP 希望 Racket 可以支持数组文字,这样它的使用就不会构成 3D 语法。我这样说是因为在 Guile 中,正则表达式不是简单的数据,因此您不能在没有创建 3D 语法的情况下在 Guile 宏中使用预编译的正则表达式,而 Racket 确实支持在没有 3D 语法的宏中直接使用正则表达式。
    • @ChrisJester-Young 是的。可扩展文字目前是 Racket 中的一个开放问题(包括我自己在内的许多人都对此感兴趣,因为 Racket 允许以多种方式扩展语言)。当前提出的解决方案相对不令人满意——它们主要涉及完全避开quote 或使quote 比现在复杂得多,破坏了quote 的整体简单性——但目前尚不清楚是否可以在没有的情况下以一般方式解决解决了很多棘手的问题,做了大量的实施工作。
    • 很好的答案,谢谢!我对您的解决方案只有一个小问题。我知道,我可以将数组的计算交给执行阶段。在简化的示例中,这是完全可以接受和合理的。我试图做的是将数组计算移至宏。换句话说,我知道在宏内部我可以做(希望这是合格的):`(define a (build-array #(20 20) (lambda (ind) ))) 但没有“build-array”之前的逗号它破坏了目的。那么我将不得不使用向量的向量(?)
    • @LechNapierała 您可以编写一个函数,将数组转换为表示产生相同数组的表达式的语法对象。也就是说,您可以编写一个将(array #[1 2]) 转换为#'(array #[1 2]) 的函数。你不能对数组中的所有潜在值都这样做,但是对于像数字这样简单的东西,它会相对简单。
    • @LechNapierała 如果可以,请使用向量而不是数组。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-13
    • 2011-01-12
    • 1970-01-01
    • 2017-12-08
    相关资源
    最近更新 更多