1

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

由于对象是可变的,一个优点似乎是您可以编写一元代码,而无需一直构造新的结果对象。

但是,我对 monads 很陌生,所以很想知道:

  • 这种结构有意义吗?
  • 它真的可以作为 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>
4

1 回答 1

3

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

想象一下,你有一个单子值m(一个值,携带一个状态),你称之为 a StateStore。你希望能够做到这一点:

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

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

monad 的优点之一是,虽然它们代表效果,但它们的行为就像普通的纯的、不可变的值。您可以传递它们,复制它们(您可以扩展任何let一元值的定义,在每个使用站点将其名称替换为它的定义)。唯一需要注意的地方是实际使用m-bind. 否则,在代码的不相关部分中没有隐式的效果链接,就像在通常的命令式编程中一样。在您想要限制副作用的情况下,这就是让推理更容易和更舒适的原因。

编辑

你可能听说过单子定律,这是任何单子实现都应该尊重的方程。但是,这里的问题不是你违法,因为法律没有谈论这个。事实上,一元法则通常用纯语言 Haskell 来表述,因此不考虑副作用。

如果您愿意,您可以将其视为第四条不言而喻的 monad 法则:好的 monad 应该尊重引用透明性

于 2010-10-23T17:53:46.143 回答