寻求对monad实施的建设性批评

时间:2011-01-22 00:23:56

标签: haskell monads idiomatic

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

此monad计算所执行的绑定次数。

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

1 个答案:

答案 0 :(得分:92)

在风格上这非常好。在现实世界中,我希望这种符号有60%的可能性而不是你给出的那种:

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

但这太小了,几乎不值得一提。

更严肃的说,不是风格而是语义:这不是一个单子。事实上,它违反了所有三个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

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

问题是f定义中的(>>=)可能会返回一个具有多个绑定的操作,而您忽略了这一点。你需要添加来自左右参数的绑定数量:

C x c >>= f = C y (c+c'+1)
   where
   C y c' = f x

这将正确计算绑定数量,并将满足第三定律,即相关性定律。它不会满足其他两个。但是,如果从此定义中删除+1,则执行会获得一个真正的monad,这相当于Writer monoid上的+ monad。这基本上将所有子计算的结果加在一起。您可以使用它来计算 somethings 的数量,只是没有绑定 - 绑定太特殊而无法计算。但是,例如:

tick :: C ()
tick = C () 1

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

事实上,您可以使用任何类型替换Int,使用任何关联运算符替换(+)并获取monad。这就是Writer monad的一般情况。如果操作员不是关联的,那么这将违反第三定律(你能明白为什么吗?)。