haskell - 寻求对 monad 实现的建设性批评

标签 haskell monads idioms

我正在学习单子(monad),这是我的第一个工作(除了琐碎的单子(monad))。随意无情地批评其中的一切。我对“更惯用”和“更优雅”的回应特别感兴趣。

这个 monad 计算执行的绑定(bind)次数。

data C a = C {value :: a, count :: Int} deriving (Show)

instance Monad C where
    (>>=) (C x c) f = C (value $ f x) (c + 1)
    return x = C x 0

add :: (Num a) => a -> a -> C a
add x y = return $ x + y

-- Simpler way to do this? foldM is obviously something different.
mysum [x] = return x
mysum (x:xs) = mysum xs >>= add x

最佳答案

从风格上讲,这是非常好的。在现实世界中,我预计这种表示法的概率为 60%,而不是你给出的那个:

C x c >>= f = C (value $ f x) (c + 1)

但这太微不足道了,几乎不值得一提。

更严肃地说,不是风格而是语义:这不是单子(monad)。事实上,它违反了所有三个单子(monad)定律。
(1) return x >>= f  =  f x
(2) m >>= return    = m
(3) m >>= (f >=> g) = (m >>= f) >>= g

(其中 (>=>) 被定义为 f >=> g = \x -> f x >>= g 。如果 (>>=) 被认为是“应用程序”运算符,则 (>=>) 是相应的组合运算符。我喜欢使用此运算符来说明第三定律,因为它带出了第三定律法律的含义:关联性。)

通过这些计算:

(1):
return 0 >>= return 
  = C 0 0 >>= return
  = C (value $ return 0) 1
  = C 0 1
Not equal to return 0 = C 0 0

(2):
C 0 0 >>= return
  = C (value $ return 0) 1
  = C 0 1
Not equal to C 0 0

(3)
C 0 0 >>= (return >=> return)
  = C (value $ (return >=> return) 0) 1
  = C (value $ return 0 >>= return) 1
  = C (value $ C 0 1) 1
  = C 0 1

Is not equal to:

(C 0 0 >>= return) >>= return
  = C (value $ return 0) 1 >>= return
  = C 0 1 >>= return
  = C (value $ return 0) 2
  = C 0 2

这不仅仅是您的实现中的错误——没有“计算绑定(bind)数量”的 monad。它必须违反法律(1)和(2)。但是,您违反法律(3)的事实是实现错误。

问题是 f(>>=) 的定义中可能会返回具有多个绑定(bind)的操作,而您将忽略它。您需要从左右参数中添加绑定(bind)数:
C x c >>= f = C y (c+c'+1)
   where
   C y c' = f x

这将正确计算绑定(bind)的数量,并满足第三定律,即结合律。它不会满足其他两个。但是,如果您放弃 +1从这个定义中,你确实得到了一个真正的 monad,它相当于 Writer + 上的单子(monad)半体。这基本上将所有子计算的结果加在一起。你可以用它来计算一些东西的数量,而不是绑定(bind)——绑定(bind)太特殊了,无法计算。但是,例如:
tick :: C ()
tick = C () 1

然后C将计算 tick 的数量计算中发生的 s。

其实可以替换Int任何类型和(+)与任何关联运算符并获得一个单子(monad)。这就是 Writer monad 是一般的。如果运算符不是关联的,那么这将不符合第三定律(你能明白为什么吗?)。

关于haskell - 寻求对 monad 实现的建设性批评,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4765260/

相关文章:

haskell - 将集合并集实现为幺半群

haskell - 尝试更好地理解 Haskell 中应用仿函数的 (<*>) 函数

haskell - 如果你违反了单子(monad)法则,你会发生什么?

haskell - 为什么在 IO Monad 中包装 IO 结果

clojure - 为clojure编写嵌套定义语句(如在方案中)的标准方法是什么?

haskell - 模式匹配 Haskell 中的 Number

haskell - 将列表转换为矩阵 (Haskell)

scala - 与try..catch相比,scala.util.Try有什么优势?

c++ - 随着 std::byte 的标准化,我们什么时候使用 void* 什么时候使用 byte*?

c++ - 通过 std::optional 标准化,我们可以停止在新代码中使用 nullptr 并弃用它吗?