Racket 中的运行时模式匹配
Runtime pattern matching in Racket
如果 Racket 的 match
宏是一个函数,我可以这样做:
(define my-clauses (list '[(list '+ x y) (list '+ y x)]
'[_ 42]))
(on-user-input
(λ (user-input)
(define expr (get-form-from-user-input user-input)) ; expr could be '(+ 1 2), for example.
(apply match expr my-clauses)))
我认为有两种截然不同的方法可以做到这一点。一种是将 my-clauses
移动到宏世界,并制作一个像这样的宏(不起作用):
(define my-clauses (list '[(list '+ x y) (list '+ y x)]
'[_ 42]))
(define-syntax-rule (match-clauses expr)
(match expr my-clauses)) ; this is not the way it's done.
; "Macros that work together" discusses this ideas, right? I'll be reading that today.
(on-user-input
(λ (user-input)
(define expr (get-form-from-user-input user-input)) ; expr could be '(+ 1 2), for example.
(match-clauses expr)))
替代方案最终可能会更好,因为它允许我在运行时更改 my-clauses
,将以某种方式在运行时执行模式匹配。有什么方法可以对运行时值使用匹配吗?
In this question Ryan Culpepper 说
It's not possible to create a function where the formal parameters and body are given as run-time values (S-expressions) without using eval
.
所以我想我必须使用 eval,但天真的方法行不通,因为 match
是一个宏
(eval `(match ,expr ,@my-clauses) (current-namespace))
我使用指南中的以下巫术得到了想要的结果
(define my-clauses '([(list'+ x y) (list '+ y x)]
[_ 42]))
(define-namespace-anchor a)
(define ns (namespace-anchor->namespace a))
(eval `(match '(+ 1 2) ,@my-clauses) ns) ; '(+ 2 1)
模式匹配现在是在运行时发生吗?这是个坏主意吗?
在最后一个示例中,模式匹配发生在运行时。
一种检查方法是查看扩展:
> (syntax->datum
(expand '(eval `(match '(+ 1 2) ,@my-clauses) ns)))
'(#%app eval (#%app list* 'match ''(+ 1 2) my-clauses) ns)
是否是个好主意...
使用 eval 相当慢,所以如果您经常调用它,最好找到其他解决方案。如果您还没有看过它,您可能想在 Racket 博客上阅读 "On eval in dynamic languages generally and in Racket specifically."。
回答问题的第一部分(假设您不一定需要在运行时提供 match
子句):
关键是:
为编译时定义my-clauses
("for syntax")。
在宏模板中正确引用。
所以:
(begin-for-syntax
(define my-clauses (list '[(list '+ x y) (list '+ y x)]
'[_ 42])))
(define-syntax (match-clauses stx)
(syntax-case stx ()
[(_ expr) #`(match expr #,@my-clauses)]))
非常感谢两位,你们的回答让我深思。我正在尝试做的事情仍然不是很明确,但我似乎在这个过程中学到了很多东西,所以这很好。
最初的想法是制作一个方程式编辑器,它是 paredit 和计算机代数系统的混合体。您输入一个初始数学 s 表达式,例如(+ x (* 2 y) (^ (- y x) 2)
。之后,程序会向您显示您通常会手动进行的步骤转换列表:替换变量、分布、因子等。就像 CAS,但一次一个步骤。当用户按下相应的组合键时,将执行转换,尽管一种可能性是只显示一堆可能的结果,并让用户从中选择表达式的新状态。对于 UI charterm 现在就可以了。
起初我以为我会让转换成为匹配表达式中的子句,但现在我想我会让它们成为采用和 return s 表达式的函数。选择编译时与运行时的麻烦在于我希望用户能够添加更多转换,并选择他自己的键绑定。这可能意味着他们在编译应用程序之前编写了一些我需要的代码,或者他们需要我的代码,所以它不会强迫我使用 eval。但如果我给用户一个 REPL 可能是最好的,这样他就可以通过编程控制表达式以及他与它的交互。
无论如何,今天我开始阅读有关宏、评估上下文和阶段的内容。我越来越喜欢球拍,我仍在研究如何制作语言......我现在将切换到修补模式,看看我是否能在我的头脑中爆发出新的东西之前得到我所描述的一些基本形式的工作想法。
如果 Racket 的 match
宏是一个函数,我可以这样做:
(define my-clauses (list '[(list '+ x y) (list '+ y x)]
'[_ 42]))
(on-user-input
(λ (user-input)
(define expr (get-form-from-user-input user-input)) ; expr could be '(+ 1 2), for example.
(apply match expr my-clauses)))
我认为有两种截然不同的方法可以做到这一点。一种是将 my-clauses
移动到宏世界,并制作一个像这样的宏(不起作用):
(define my-clauses (list '[(list '+ x y) (list '+ y x)]
'[_ 42]))
(define-syntax-rule (match-clauses expr)
(match expr my-clauses)) ; this is not the way it's done.
; "Macros that work together" discusses this ideas, right? I'll be reading that today.
(on-user-input
(λ (user-input)
(define expr (get-form-from-user-input user-input)) ; expr could be '(+ 1 2), for example.
(match-clauses expr)))
替代方案最终可能会更好,因为它允许我在运行时更改 my-clauses
,将以某种方式在运行时执行模式匹配。有什么方法可以对运行时值使用匹配吗?
In this question Ryan Culpepper 说
It's not possible to create a function where the formal parameters and body are given as run-time values (S-expressions) without using
eval
.
所以我想我必须使用 eval,但天真的方法行不通,因为 match
是一个宏
(eval `(match ,expr ,@my-clauses) (current-namespace))
我使用指南中的以下巫术得到了想要的结果
(define my-clauses '([(list'+ x y) (list '+ y x)]
[_ 42]))
(define-namespace-anchor a)
(define ns (namespace-anchor->namespace a))
(eval `(match '(+ 1 2) ,@my-clauses) ns) ; '(+ 2 1)
模式匹配现在是在运行时发生吗?这是个坏主意吗?
在最后一个示例中,模式匹配发生在运行时。
一种检查方法是查看扩展:
> (syntax->datum
(expand '(eval `(match '(+ 1 2) ,@my-clauses) ns)))
'(#%app eval (#%app list* 'match ''(+ 1 2) my-clauses) ns)
是否是个好主意...
使用 eval 相当慢,所以如果您经常调用它,最好找到其他解决方案。如果您还没有看过它,您可能想在 Racket 博客上阅读 "On eval in dynamic languages generally and in Racket specifically."。
回答问题的第一部分(假设您不一定需要在运行时提供 match
子句):
关键是:
为编译时定义
my-clauses
("for syntax")。在宏模板中正确引用。
所以:
(begin-for-syntax
(define my-clauses (list '[(list '+ x y) (list '+ y x)]
'[_ 42])))
(define-syntax (match-clauses stx)
(syntax-case stx ()
[(_ expr) #`(match expr #,@my-clauses)]))
非常感谢两位,你们的回答让我深思。我正在尝试做的事情仍然不是很明确,但我似乎在这个过程中学到了很多东西,所以这很好。
最初的想法是制作一个方程式编辑器,它是 paredit 和计算机代数系统的混合体。您输入一个初始数学 s 表达式,例如(+ x (* 2 y) (^ (- y x) 2)
。之后,程序会向您显示您通常会手动进行的步骤转换列表:替换变量、分布、因子等。就像 CAS,但一次一个步骤。当用户按下相应的组合键时,将执行转换,尽管一种可能性是只显示一堆可能的结果,并让用户从中选择表达式的新状态。对于 UI charterm 现在就可以了。
起初我以为我会让转换成为匹配表达式中的子句,但现在我想我会让它们成为采用和 return s 表达式的函数。选择编译时与运行时的麻烦在于我希望用户能够添加更多转换,并选择他自己的键绑定。这可能意味着他们在编译应用程序之前编写了一些我需要的代码,或者他们需要我的代码,所以它不会强迫我使用 eval。但如果我给用户一个 REPL 可能是最好的,这样他就可以通过编程控制表达式以及他与它的交互。
无论如何,今天我开始阅读有关宏、评估上下文和阶段的内容。我越来越喜欢球拍,我仍在研究如何制作语言......我现在将切换到修补模式,看看我是否能在我的头脑中爆发出新的东西之前得到我所描述的一些基本形式的工作想法。