【问题标题】:Clojure simple pattern matchingClojure 简单模式匹配
【发布时间】:2011-06-14 18:19:18
【问题描述】:

作为我previous question 的后续行动,我正在尝试在 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

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

我正在考虑将每个 case-and-clause 扩展为 let,并绑定到内部变量,并检查文字符号 ('&lt;) 是否与模式匹配。也许是第二种模式(['&lt; 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 部分是否为向量。这种模式匹配应该发生在这种情况下。

【问题讨论】:

  • 您是否查看过现有的 Clojure 模式匹配工具?例如 matchure 看起来很有趣:spin.atomicobject.com/2010/04/25/…
  • @mikera:是的。我只是想弄清楚宏,并希望编写它们会有所启发。
  • 啊,那太酷了。只要你想学习造轮子的艺术,重新发明轮子就完美了!

标签: macros clojure pattern-matching


【解决方案1】:

我的第一个想法基本上是相同的:将东西绑定到内部本地人并在一个大的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 点盯着天花板思考这个问题 :-)。在探索你的想法之前,我会先尝试一下我的想法,看看我能否让它发挥作用。毕竟,这整件事是学习宏的练习(主要是)。我的想法是遍历模式/表达式列表并形成 cond 子句,其中包含文字测试和绑定的 let 块。顺便说一句,Doug Hoyte 为 Common Lisp 编写的“Let Over Lambda”给了我一些关于 Clojure 宏的好主意。
  • @ralph 你应该明确地探索你的想法!我决定反对cond,顺便说一句。也许您有类似的考虑,但决定这样做。让我们来看看。总而言之,我很高兴有可用的宏,但发现我在 Clojure 中并不经常使用它们。
  • @ralph 最后看看 matchjure 看看专家是怎么做的。 ;)
  • @kotarak:查看您的扩展,我发现每个新模式都嵌套在前一个模式中。这意味着每个模式都可以“看到”前一个模式的绑定。阴影将隐藏被覆盖的符号,但其他绑定仍然可见。这可能是也可能不是问题,这取决于您希望它的行为方式。我正在考虑嵌套let 块的方法,以便每个模式都隐藏其与后续模式的绑定。也许这就是您选择 if 语句而不是 cond 的原因。
  • @kotarak:“我还添加了检查规范向量是否匹配目标向量的长度。”昨晚我也在考虑这个问题。另一个扩展可能是支持 [item item ... :when condition] 并允许类似于 Scala 中的模式。项目计数必须与目标计数相匹配。您还可以允许 :val symbol 被符号的当前绑定替换。我的想法正在成长为 matchure 的完整实现:-)。
猜你喜欢
  • 2011-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-25
  • 2015-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多