Clojure简单模式匹配

4
作为对我之前提出的问题(链接)的跟进,我正在尝试在Clojure中实现简单的模式匹配。
我想要实现类似以下的功能:
(match target
  [ub]    expr1    ; ub should be bound to actual value in expr1
  ['< ub] expr2    ; match the literal less-than symbol
                   ; and ub should be bound to actual value in expr2
  [lb ub] expr3    ; lb and ub should be bound to actual values in expr3
  :else   expr4    ; default case if none match
)

使用方法:

(match [< 5.0] ...)

应该在运行时安排执行expr2

我想写一个宏,但我不确定扩展的内容。

我正在考虑让每个情况和子句扩展为一个带有内部变量绑定和检查字面符号('<)实际匹配模式的let。也许对于第二个模式(['< ub]):

(let [[sym1 ub] pattern]
  (if (= '< sym1)
    expr1)

我需要在绑定中使用 (gensym) 吗?如何使用?

更大的图景:

(range-case target
            [0.0 < 1.0] :greatly-disagree
            [< 2.0]     :disagree
            [< 3.0]     :neutral
            [< 4.0]     :agree
            [5.0]       :strongly-agree
            42          :the-answer
            :else       :do-not-care)

我正在尝试匹配 [...] 模式并将其转换为以下内容:

[ub]          (if previous-ub `(and (<= ~previous-ub ~target) (<= ~target ~ub))
                              `(< ~target ~ub))
['< ub]       (if previous-ub `(and (<= ~previous-ub ~target) (< ~target ~ub))
                              `(< ~target ~ub))
[lb ub]       `(and (<= ~lb ~target) (<= ~target ~ub))
['< lb ub]    `(and (< ~lb ~target) (<= ~target ~ub))
[lb '< ub]    `(and (<= ~lb ~target) (< ~target ~ub))
['< lb '< ub] `(and (< ~lb ~target) (< ~target ~ub))

我有一个cond,用于检查case部分是否为向量。这个模式匹配应该出现在该case内。


你有没有看过现有的Clojure模式匹配工具?例如,Matchure看起来非常有趣:http://spin.atomicobject.com/2010/04/25/matchure-serious-clojure-pattern-matching/ - mikera
@mikera:是的。我正在努力弄清楚宏,希望通过编写它们来有所启示。 - Ralph
啊,那很棒。只要你试图学习造轮子的艺术,重新发明轮子就完全没问题! - mikera
1个回答

3

我的第一个想法基本相同:将内容绑定到内部变量,并在大的 and 中测试其内容。对于字面值,该值绑定到生成的本地变量;符号直接用于绑定。

我还添加了一项检查,以确保规范向量与目标向量的长度相匹配。否则,您不能同时拥有[ub][lb ub],因为两者都不包含可能失败的检查。所以总是选择第一个。

以下是代码:

(defn make-clause
  [expr-g [spec expr & more :as clause]]
  (when (seq clause)
    (let [tests-and-bindings (map (fn [x]
                                    (if-not (symbol? x)
                                      (let [x-g (gensym "x")]
                                        [`(= ~x ~x-g) x-g])
                                      [nil x]))
                                  spec)
          tests    (keep first tests-and-bindings)
          bindings (map second tests-and-bindings)]
      `(let [[~@bindings] ~expr-g]
         (if (and (= (count ~expr-g) ~(count spec)) ~@tests)
           ~expr
           ~(make-clause expr-g more))))))

(defmacro match
  [expr & clauses]
  (let [expr-g  (gensym "expr")]
    `(let ~[expr-g expr]
       ~(make-clause expr-g clauses))))

以下是一个扩展示例。我在示例中没有使用语法引用以减少扩展中的噪音,但您应该能够理解。

(let [expr98 [(quote <) 3.0]]
  (let [[ub] expr98]
    (if (and (= (count expr98) 1))
      (if previous-ub
        (and (<= previous-ub target) (<= target ub))
        (< target ub))
      (let [[x99 ub] expr98]
        (if (and (= (count expr98) 2) (= (quote <) x99))
          (if previous-ub
            (and (<= previous-ub target) (< target ub))
            (< target ub))
          (let [[lb ub] expr98]
            (if (and (= (count expr98) 2))
              (and (<= lb target) (<= target ub))
              nil)))))))

调用如下所示:
(match ['< 3.0]
  [ub]    (if previous-ub
            (and (<= previous-ub target) (<= target ub))
            (< target ub))
  ['< ub] (if previous-ub
            (and (<= previous-ub target) (< target ub))
            (< target ub))
  [lb ub] (and (<= lb target) (<= target ub))))

希望这能帮助你开始入门。

@kotarak:我在凌晨1点盯着天花板思考这个问题:-)。在探索你的想法之前,我要先尝试我的想法,看看能否让它正常工作。毕竟,整个过程都是学习宏(主要是)。我的想法是遍历模式/表达式列表,并使用字面量进行测试和绑定的let块来形成cond子句。顺便说一下,“Let Over Lambda”(Doug Hoyte著)虽然是为Common Lisp编写的,但为我在Clojure中编写宏提供了一些很好的思路。 - Ralph
@ralph 最后看一下 matchjure,看看专家们是如何做的。 ;) - kotarak
@kotarak:看到你的扩展,我发现每个新模式都嵌套在前一个模式内。这意味着每个模式可以“看到”前一个模式的绑定。遮蔽将隐藏被覆盖的符号,但其他绑定仍然可见。这可能是一个问题,也可能不是,这取决于您希望它如何运作。我正在考虑嵌套let块的方法,以便每个模式都会隐藏其后续模式的绑定。也许这就是您选择if语句而不是cond的原因。 - Ralph
@kotarak:“我还添加了一个检查,确保规范向量与目标向量的长度相匹配。”昨晚我也在考虑这个问题。另一个扩展可能是支持[item item ... :when condition]并允许类似于Scala中的模式。项目计数必须与目标计数匹配。您还可以允许:val symbol,它将被替换为符号的当前绑定。我的想法正在发展成为matchure的完整实现:-)。 - Ralph
@kotarak:你可以将let块移动到每个if语句的“then”或“else”部分。在“心理上”扩展cond并查看其结构。cond“隔离”其子句。顺便说一句,matchure有一种很好的绑定变量的方法:?foo - Ralph
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接