开发者

Seeking constructive criticism on monad implementation

开发者 https://www.devze.com 2023-02-06 06:26 出处:网络
I\'m learning monads, this is my first working one (aside from the trivial monad). Feel free to criticize everything in it ruthlessly. I\'m especially interested in \"more idiomatic\" and \"more elega

I'm learning monads, this is my first working one (aside from the trivial monad). Feel free to criticize everything in it ruthlessly. I'm especially interested in "more idiomatic" and "more elegant" kind of responses.

This monad counts the number of bind开发者_如何学运维s performed.

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


Stylistically this is very nice. In the real world, I would expect a 60% chance of this notation instead of the one you gave:

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

But that is so minor it is hardly worth mentioning.

On a more serious note, not stylistic but semantic: this is not a monad. In fact, it violates all three of the monad laws.

(1) return x >>= f  =  f x
(2) m >>= return    = m
(3) m >>= (f >=> g) = (m >>= f) >>= g

(Where (>=>) is defined as f >=> g = \x -> f x >>= g. If (>>=) is considered an "application" operator, then (>=>) is the corresponding composition operator. I like to state the third law using this operator because it brings out the third law's meaning: associativity.)

With these computations:

(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

This isn't just an error in your implementation -- there is no monad that "counts the number of binds". It must violate laws (1) and (2). The fact that yours violates law (3) is an implementation error, however.

The trouble is that f in the definition of (>>=) might return an action that has more than one bind, and you are ignoring that. You need to add the number of binds from the left and right arguments:

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

This will correctly count the number of binds, and will satisfy the third law, which is the associativity law. It won't satisfy the other two. However, if you drop the +1 from this definition, then you do get a real monad, which is equivalent to the Writer monad over the + monoid. This basically adds together the results of all subcomputations. You could use this to count the number of somethings, just not binds -- bind is too special to count. But, for example:

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

Then C will count the number of ticks that occurred in the computation.

In fact, you can replace Int with any type and (+) with any associative operator and get a monad. This is what a Writer monad is in general. If the operator is not associative, then this will fail the third law (can you see why?).

0

精彩评论

暂无评论...
验证码 换一张
取 消