我一直在Clojure中使用monad进行实验,并提出了以下代码,其中一个可变值Clojure deftype对象表示一个monadic值/状态对。

由于对象是可变的,因此似乎有一个优点是您可以编写单子代码而无需始终构造新的结果对象。

但是,我对Monad并不陌生,所以很想知道:


这种结构有意义吗?
它实际上可以作为monad正常工作吗?


代码如下:

(defprotocol PStateStore
  (set-store-state [ss v])
  (get-store-state [ss])
  (set-store-value [ss v])
  (get-store-value [ss]))

(deftype StateStore [^{:unsynchronized-mutable true} value
                     ^{:unsynchronized-mutable true} state]
  PStateStore
      (get-store-state [ss] (.state ss))
      (get-store-value [ss] (.value ss))
      (set-store-state [ss v] (set! state v))
      (set-store-value [ss v] (set! value v))

   Object
     (toString [ss] (str "value=" (.value ss) ", state=" (.state ss))))

(defn state-store [v s] (StateStore. v s))

(defmonad MStoredState
  [m-result (fn [v]
              (fn [^StateStore ss]
                  (do
                    (set-store-value ss v)
                    ss)))
   m-bind (fn [a f]
            (fn [^StateStore ss]
              (do
                (a ss)
                ((f (get-store-value ss)) ss))))])

; Usage examples

(def mb
  (domonad MStoredState
    [a (m-result 1)
     b (m-result 5)]
    (+ a b)))

(def ssa (state-store 100 101))

(mb ssa)

; => #<StateStore value=6, state=101>

最佳答案

不,它不能作为monad正常工作,因为您使用可变状态。

假设您有一个单价值m(一个带有状态的值),称为StateStore。您希望能够做到这一点:

(let
   [a (incr-state m)
    b (decr-state m)]
  (if some-condition a b))


我期望的是,此计算返回单子m的状态,根据some-condition,其状态已递增或递减。如果使用可变状态,则在评估此代码期间将同时增加和减少。

关于monad的优点之一是,尽管它们代表效果,但它们的行为就像普通的纯净的,非可变的值。您可以传递它们,复制它们(您可以扩展任何单值的let定义,并在每个使用地点使用其定义替换其名称)。您唯一需要注意的地方是实际上使用m-bind链接效果的位置。否则,就不会像通常的命令式编程那样在代码的不相关部分中隐含效果链。这就是在您希望限制副作用的情况下,使有关monad的推理更轻松,更舒适的原因。

编辑

您可能听说过单子法则,这是任何单子法实施都应遵循的方程式。但是,这里的问题不是您违反法律,因为法律没有提及这一点。确实,单子法通常是用纯语言Haskell陈述的,因此不考虑副作用。

如果愿意,可以将其视为第四条无言的单子法:好的单子应尊重referential transparency

07-28 01:51
查看更多