Sam Liao
Sam Liao

Reputation: 46103

How to understand "m ()" is a monadic computation

From the document:

when :: (Monad m) => Bool -> m () -> m ()
when p s = if p then s else return ()

The when function takes a boolean argument and a monadic computation with unit () type and performs the computation only when the boolean argument is True.

===

As a Haskell newbie, my problem is that for me m () is some "void" data, but here the document mentions it as computation. Is it because of the laziness of Haskell?

Upvotes: 2

Views: 118

Answers (3)

Random Dev
Random Dev

Reputation: 52290

Laziness has no part in it.

The m/Monad part is often called computation.

The best example might be m = IO:

Look at putStrLn "Hello" :: IO () - This is a computation that, when run, will print "Hello" to your screen.

This computation has no result - so the return type is ()

Now when you write

hello :: Bool -> IO ()
hello sayIt =
   when sayIt (putStrLn "Hello")

then hello True is a computation that, when run, will print "Hello"; while hello False is a computation that when run will do nothing at all.


Now compare it to getLine :: IO String - this is a computation that, when run, will prompt you for an input and will return the input as a String - that's why the return type is String.

Does this help?

Upvotes: 6

Luis Casillas
Luis Casillas

Reputation: 30237

Monads are notably abstract and mathematical, so intuitive statements about them are often made in language that is rather vague. So values of a monadic type are often informally labeled as "computations," "actions" or (less often) "commands" because it's an analogy that sometimes help us reason about them. But when you dig deeper, these turn out to be empty words when used this way; ultimately what they mean is "some value of a type that provides the Monad interface."

I like the word "action" better for this, so let me go with that. The intuition for the use for that word in Haskell is this: the language makes a distinction between functions and actions:

  1. Functions can't have any side effects, and their types look like a -> b.
  2. Actions may have side effects, and their types look like IO a.
    • A consequence of this: an action of type IO () produces an uninteresting result value, and therefore it's either a no-op (return ()) or an action that is only interesting because of its side effects.

Monad then is the interface that allows you to glue actions together into complex actions.

This is all very intuitive, but it's an analogy that becomes rather stretched when you try to apply it to many monads other than the IO type. For example, lists are a monad:

instance Monad [] where
  return a = [a]
  as >>= f = concatMap f as

The "actions" or "computations" of the list monad are... lists. How is a list an "action" or a "computation"? The analogy is rather weak in this case, isn't it?

So I'd say that this is the best advice:

  1. Understand that "action" and "computation" are analogies. There's no strict definition.
  2. Understand that these analogies are stronger for some monad instances, and weak for others.
  3. The ultimate barometer of how things work are the Monad laws and the definitions of the various functions that work with Monad.

Upvotes: 2

Bartek Banachewicz
Bartek Banachewicz

Reputation: 39390

for me "m ()" is some "void" data

And that kinda makes sense, in that a computation is a special kind of data. It has nothing to do with laziness - it's associated with context.

Let's take State as an example. A function of type, say, s -> () in Haskell can only produce one value. However, a function of type s -> ((), s) is a regular function doing some transformation on s. The problem you're having is that you're only looking at the () part, while the s -> s part stays hidden. That's the point of State - to hide the state passing.

Hence State s () is trivially convertible to s -> ((), s) and back, and it still is a Monad (a computation) that produces a value of... ().

If we look at practical use, now:

(flip runState 10) $ do
    modify (+1)

This expression produces a tuple of ((), Int); the Int part is hidden

It will modify the state, adding 1 to it. It produces the intermediate value of (), though, which fits your when:

when (5 > 3) $ modify (+1)

Upvotes: 2

Related Questions