我正在学习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
答案 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的一般情况。如果操作员不是关联的,那么这将违反第三定律(你能明白为什么吗?)。