如何在更大的Clojure程序中以实用的方式使用core.logic?

时间:2013-03-07 00:13:37

标签: clojure clojure-core.logic

我无法绕过如何混合clojure和core.logic。

例如说我有以下问题:

我有一个带有附加分数的键值对列表:

(:foo "10" 2)
(:bar "20" 3)
(:baz "30" 7)

我还有一张地图:

{:foo "10",
 :bar "42",
 :baz "30"}

我想做的是,根据地图评估的分数列表返回分数列表。

使用核心逻辑我可以这样做:

(defrel score key value score)
(fact score :foo "10" 2)
(fact score :bar "20" 3)
(fact score :baz "30" 7)

(run* [q]
  (conde 
    ((score :foo "10" q))
    ((score :baz "30" q))))

我得到了预期的结果:

(2 7)

我的问题是我没有看到如何将其变成我可以动态地在更大的程序中运行的东西。这意味着我将在不同的时间应用不同的地图和不同的约束。我想我可以通过编写一个获取地图并输出约束的函数来创建conde的参数,但是我该如何运行*  在一组临时事实的背景下进行评估?

我当然可以编写一个函数来返回我想要的东西而不用core.logic,但这看起来不太优雅。也许我正在咆哮错误的树(我是Clojure和core.logic的新手),这根本不是一个约束问题。

所以我的问题是:

当你从直到运行时才知道的来源提取事实和约束时,你如何混合核心逻辑?

相关的,你如何在一个环境中这样做,你想在一个新的事实环境中评估一组约束?

2 个答案:

答案 0 :(得分:4)

最重要的是要记住:关系只是回归目标的功能。 目标是一个可以succeedfail的函数,所以基本上关系只是更高阶的函数。

现在你可以举例说明关系和各种事实都在一个函数中,没有“全局”关系/事实可以相互干扰:

(defn find-things []
  (letfn [(scoref [key value score]
            (conde
             [(== key :foo) (== value "10") (== score 2)]
             [(== key :bar) (== value "20") (== score 3)]
             [(== key :baz) (== value "30") (== score 7)]))]
    (run* [q]
          (conde 
           ((scoref :foo "10" q))
           ((scoref :baz "30" q))))))

score只是一个返回目标的函数(使用conde宏)

这解决了本地/全局关系问题,但事实和查询仍硬编码到我们希望作为参数传递的函数中。 一种可能的方法是理解core.logic API,它允许您定义动态逻辑变量并统一它们等。我没有通过该API,所以我将无法使用它来回答。另一种方法是使用宏和eval魔法:

(defmacro find-things-generator [data query]
  (let [key (gensym) value (gensym) score (gensym) q (gensym)]
    `(letfn [(~'scoref [~key ~value ~score]
               (conde
                ~@(map #(-> [`(== ~key ~(% 0))
                             `(== ~value ~(% 1))
                             `(== ~score ~(% 2))]) data)
                ))]
       (run* [~q]
             (conde
              ~@(map #(-> [`(~'scoref ~(% 0) ~(% 1) ~q)]) query)
              )))))


(defn find-things [data query]
  (eval `(find-things-generator ~data ~query)))

(def data [[:foo "1" 2]
           [:bar "2" 3]
           [:baz "3" 7]])

(def query {:foo "1",
            :bar "2",
            :baz "3"})

(find-things data query)

答案 1 :(得分:3)

我有一个类似的问题,这就是我想出来的,转化为你的问题。

定义您的分数集合。

(def scores
  [[:foo "10" 2]
   [:bar "20" 3]
   [:baz "30" 7]])

接下来,定义一个将分数转换为关系形式的函数。

(defn scoreso [key value score scores]
  (conde
    [(fresh [a]
       (firsto scores a)
       (== [key value score] a))]
    [(fresh [d]
      (resto scores d)
       (scoreso key value score d))]))

最后,确定向量中的哪些分数与给定的键和值匹配。

(run* [score]
  (fresh [key value]
    (scoreso key value score scores)
    (conde
      [(== key :foo) (== value "10")]
      [(== key :baz) (== value "30")])))

结果是(2 7)。

查询的措辞不同,但它是等效的。