Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
819 views
in Technique[技术] by (71.8m points)

haskell - Seeking constructive criticism on monad implementation

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 binds 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
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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?).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...