这是 Clojure 中可变状态的合理单子吗?

发布于 2024-09-28 07:45:58 字数 1340 浏览 7 评论 0原文

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

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

然而,我对 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>

I've been experimenting with monads in Clojure and came up with the following code, where a monadic value/state pair is represented by a mutable Clojure deftype object.

Since the object is mutable, an advantage would seem to be that you can write monadic code without needing to construct new result objects all the time.

However, I'm pretty new to monads so would love to know:

  • Does this construct make sense?
  • Will it actually work correctly as a monad?

Code below:

(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>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

想你的星星会说话 2024-10-05 07:45:58

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

想象一下,您有一个一元值m(一个带有状态的值),您将其称为StateStore。您希望能够做到这一点:

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

我期望的是,此计算返回一元 m ,其状态根据 some-condition 递增或递减。如果您使用可变状态,则在评估此代码期间,它会同时递增和递减。

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

编辑

您可能听说过monadic law,这是任何 monad 实现都应该遵守的方程式。然而,你这里的问题并不是你违反了法律,因为法律没有谈论这一点。事实上,一元法则通常用纯语言 Haskell 来表述,因此不考虑副作用。

如果你愿意,你可以将其视为第四条不言而喻的 monad 法则:好的 monad 应该尊重 参考透明度

No, it won't work correctly as a monad, because you use mutable state.

Imagine that you have a monadic value m (a value, carrying a state), which you call a StateStore. You want to be able to do this :

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

What I expect is that this computation returns the monadic m whose state has been either, according to some-condition, incremented or decremented. If you use mutable state, it will be both incremented and decremented during evaluation of this code.

One of the good things about monads is that, while they represent effects, they behave as ordinary pure, non-mutable values. You can pass them around, duplicate them (you can expand any let-definition of a monadic value, replacing its name with it's definition at every use site). The only place you have to be careful is where you actually chain effect using m-bind. Otherwise, there is no implicit chaining of effects in unrelated parts of code, as in usual imperative programming. This is what makes reasoning about monads easier and more comfortable, in the situations where you want to restrict side-effects.

Edit

You may have heard of the monadic laws, which are equations that any monad implementation should respect. However, your problem here is not that you break the law, as the law don't talk about this. Indeed, the monadic laws are usually stated in the pure language Haskell, and therefore do not consider side-effects.

If you want, you could consider it as a fourth, unspoken, monad law : good monads should respect referential transparency

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文