3

我在写的一些单子中出现了意想不到的行为。我创建了一个 parser-m monad

(def parser-m (state-t maybe-m))

这几乎是到处给出的例子(这里这里这里

我使用 m-plus 作为一种贯穿查询机制,在我的例子中,它首先从缓存(数据库)中读取值,如果返回 nil,下一个方法是从“live”中读取( REST 调用)。

但是,总是调用 m-plus 列表中的第二个值,即使它的值被忽略(如果缓存命中良好)并且最终返回的是第一个单子函数的值。

这是我看到的问题的简化版本,以及我找到的一些解决方案,但我不知道为什么。

我的问题是:

  1. 这是预期的行为还是 m-plus 中的错误?即,即使第一项返回值,m-plus 列表中的第二个方法是否总是被评估?
  2. 与上述相比较小,但如果我 _ (fetch-state)从检查器中删除调用,当我评估该方法时,它会打印出 m-plus 正在调用的函数的消息(当我认为它不应该调用时)。这也是bug吗?

这是有问题的代码的精简版本,突出了问题。它只是检查传入的键/值对是否与初始状态值相同,并更新状态以标记它实际运行的内容。

(ns monads.monad-test
  (:require [clojure.algo.monads :refer :all]))

(def parser-m (state-t maybe-m))

(defn check-k-v [k v]
  (println "calling with k,v:" k v)
  (domonad parser-m
           [kv (fetch-val k)
            _ (do (println "k v kv (= kv v)" k v kv (= kv v)) (m-result 0))
            :when (= kv v)
            _ (do (println "passed") (m-result 0))
            _ (update-val :ran #(conj % (str "[" k " = " v "]")))
            ]
           [k v]))

(defn filler []
  (println "filler called")
  (domonad parser-m
           [_ (fetch-state)
            _ (do (println "filling") (m-result 0))
            :when nil]
           nil))

(def checker
  (domonad parser-m
           [_ (fetch-state)
            result (m-plus
                    ;; (filler) ;; intitially commented out deliberately
                    (check-k-v :a 1)
                    (check-k-v :b 2)
                    (check-k-v :c 3))]
           result))

(checker {:a 1 :b 2 :c 3 :ran []})

当我按原样运行时,输出是:

> (checker {:a 1 :b 2 :c 3 :ran []})
calling with k,v: :a 1
calling with k,v: :b 2
calling with k,v: :c 3
k v kv (= kv v) :a 1 1 true
passed
k v kv (= kv v) :b 2 2 true
passed
[[:a 1] {:a 1, :b 2, :c 3, :ran ["[:a = 1]"]}]

我不希望这条线k v kv (= kv v) :b 2 2 true出现。最终结果是从第一个函数返回给 m-plus 的值,正如我所料,但我不希望第二个函数被调用。

现在,我发现如果我将填充符传递给不执行任何操作的 m-plus(即取消注释该(filler)行),那么输出是正确的,不会评估 :b 值。

如果我没有填充方法,并使第一个方法测试失败(即(check-k-v :a 2)再次将其更改为 then 一切都很好,我没有接到检查 :c 的电话,只有 a 和 b 被测试。

根据我对state-t maybe-m转换给我的理解,m-plus 函数应该如下所示:

(defn m-plus
  [left right]
  (fn [state]
    (if-let [result (left state)]
      result
      (right state))))

这意味着right除非left返回 nil/false,否则不会调用它。

编辑: 在查看 state-t 和可能 -m 源之后,m-plus 看起来更像:

(fn [& statements]
  (fn [state]
    (apply (fn [& xs]
             (first (drop-while nil? xs)))
           (map #(% state) statements))))

但原理是一样的,(first (drop-while nil? ...)只应在返回有效值的项目上执行。

我很想知道我的理解是否正确,以及为什么我必须使用填充方法来停止额外的评估(我不想发生这些影响)。

编辑:

如果我转而使用Jim Duey 的 parser-m 手写实现(来自他的优秀博客),则不会评估 m-plus 中的第二个函数,这似乎意味着转换 monad 正在破坏 m-plus。然而,即使在这个实现中,如果我删除函数中的初始(fetch-state)调用checker,domonad 定义会导致创建 m-plus 函数的输出,这表明在 domonad 的实现中会发生一些我没有预料到的事情。

为冗长的帖子道歉!

4

0 回答 0