如何在匹配子句中重用匹配模式?

How to reuse matching patterns in match clauses?

我需要匹配两种元组并从中生成映射。

两者都有关键字和字符串。可以有第三项(语言代码)。

[<key> <value>]        ~> {:type <key> :value <value>}
[<key> <value> <lang>] ~> {:type <key> :value <value> :lang <lang>}

我只需要匹配关键字为 :foo:bar 的关键字,并决定使用 clojure.core.match:

(ns so.example
  (:require
   [clojure.core.match :refer [match]]))

(defn example-1 [ast]
  (let [l10n-key #{:foo :bar}]
    (match ast
      [(k :guard l10n-key) v lang] {:type k :value v :lang lang}
      [(k :guard l10n-key) v]      {:type k :value v})))

(example-1 [:foo 10])
;=> {:type :foo, :value 10}

(example-1 [:bar 20 "en"])
;=> {:type :bar, :value 20, :lang "en"}

可行,但我想在不同的子句中重复使用匹配模式 :guard l10n-key。所以我想我可以使用一些语法引用和取消引用拼接:

(defn example-2 [ast]
  (let [l10n-key-match [:guard #{:foo :bar}]]
    (match ast
      [`(k ~@l10n-key-match) v lang] {:type k :value v :lang lang}
      [`(k ~@l10n-key-match) v]      {:type k :value v})))

但是 defn 表达式崩溃:

Unexpected error (AssertionError) macroexpanding match at (form-init11111096422056977084.clj:3:5).
Invalid list syntax (clojure.core/concat (clojure.core/list (quote so.example/k)) l10n-key-match) in (clojure.core/seq (clojure.core/concat (clojure.core/list (quote so.example/k)) l10n-key-match)). Valid syntax: [[:default :guard] [:or :default] [:default :only] [:default :seq] [:default :when] [:default :as] [:default :<<] [:default :clojure.core.match/vector]]

我做错了什么?

没有解决 clojure.core.match 的问题,但对于这么简单的事情你真的不需要它:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(dotest
  (let [data      [[:foo 10]
                   [:bar 20 "en"]
                   [:fizz 10]
                   [:buzz 20 "en"]]
        keep-tags #{:foo :bar}
        data-keep (filterv #(contains? keep-tags (first %)) data)
        result    (forv [tuple data-keep]
                    (zipmap [:type :value :lang] tuple))]
    (is= result [{:type :foo, :value 10}
                 {:type :bar, :value 20, :lang "en"}])))

您可能还对这些辅助函数感兴趣:

这不就是 Clojure 附带的 spec 吗?你会像

这样定义你的模式
(ns playground.catspec
  (:require [clojure.spec.alpha :as spec]))

(spec/def ::type #{:foo :bar})
(spec/def ::value number?)
(spec/def ::lang #{"en" "sv" "fr"})

(spec/def ::key-value-lang (spec/cat :type ::type
                                     :value ::value
                                     :lang (spec/? ::lang)))

我们使用 spec/def 定义规范,spec/cat 连接规范,spec/? 定义可选规范。

然后我们使用conform解析元组:

(spec/conform ::key-value-lang [:foo 10])
;; => {:type :foo, :value 10}

(spec/conform ::key-value-lang [:bar 20 "en"])
;; => {:type :bar, :value 20, :lang "en"}

(spec/conform ::key-value-lang [:bar 119 "fr"])
;; => {:type :bar, :value 119, :lang "fr"}

(spec/conform ::key-value-lang [119 :foo])
;; => :clojure.spec.alpha/invalid

(spec/conform ::key-value-lang [:bar 119 "uj"])
;; => :clojure.spec.alpha/invalid

(spec/conform ::key-value-lang [:bar])
;; => :clojure.spec.alpha/invalid

(spec/conform ::key-value-lang [:bar 119 "fr" :asdfasdf])
;; => :clojure.spec.alpha/invalid

(spec/conform ::key-value-lang {:a 1 :b 4})
;; => :clojure.spec.alpha/invalid