10

In "Learn You a Haskell for Great Good!" author claims that Applicative IO instance is implemented like this:

instance Applicative IO where
    pure = return
    a <*> b = do
        f <- a
        x <- b
        return (f x)

I might be wrong, but it seems that both return, and do-specific constructs (some sugared binds (>>=) ) comes from Monad IO. Assuming that's correct, my actual question is:

Why Applicative IO implementation depends on Monad IO functions/combinators?

Isn't Applicative less powerfull concept than Monad?


Edit (some clarifications):

This implementation is against my intuition, because according to Typeclassopedia article it's required for a given type to be Applicative before it can be made Monad (or it should be in theory).

gorsky
  • 2,282
  • 1
  • 16
  • 22

5 Answers5

12

(...) according to Typeclassopedia article it's required for a given type to be Applicative before it can be made Monad (or it should be in theory).

Yes, your parenthetical aside is exactly the issue here. In theory, any Monad should also be an Applicative, but this is not actually required, for historical reasons (i.e., because Monad has been around longer). This is not the only peculiarity of Monad, either.

Consider the actual definitions of the relevant type classes, taken from the base package's source on Hackage.

Here's Applicative:

class Functor f => Applicative f where
    pure  :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b
    (*>)  :: f a -> f b -> f b
    (<*)  :: f a -> f b -> f a

...about which we can observe the following:

  • The context is correct given currently existing type classes, i.e., it requires Functor.
  • It's defined in terms of function application, rather than in (possibly more natural from a mathematical standpoint) terms of lifting tuples.
  • It includes technically superfluous operators equivalent to lifting constant functions.

Meanwhile, here's Monad:

class Monad m where
    (>>=)       :: m a -> (a -> m b) -> m b
    (>>)        :: m a -> m b -> m b
    return      :: a -> m a
    fail        :: String -> m a

...about which we can observe the following:

  • The context not only ignores Applicative, but also Functor, both of which are logically implied by Monad but not explicitly required.
  • It's also defined in terms of function application, rather than the more mathematically natural definition using return and join.
  • It includes a technically superfluous operator equivalent to lifting a constant function.
  • It also includes fail which doesn't really fit in at all.

In general, the ways that the Monad type class differs from the mathematical concept it's based on can be traced back through its history as an abstraction for programming. Some, like the function application bias it shares with Applicative, are a reflection of existing in a functional language; others, like fail or the lack of an appropriate class context, are historical accidents more than anything else.

What it all comes down to is that having an instance of Monad implies an instance for Applicative, which in turn implies an instance for Functor. A class context merely formalizes this explicitly; it remains true regardless. As it stands, given a Monad instance, both Functor and Applicative can be defined in a completely generic way. Applicative is "less powerful" than Monad in exactly the same sense that it is more general: Any Monad is automatically Applicative if you copy+paste the generalized instance, but there exist Applicative instances which cannot be defined as a Monad.

A class context, like Functor f => Applicative f says two things: That the latter implies the former, and that a definition must exist to fulfill that implication. In many cases, defining the latter implicitly defines the former anyway, but the compiler cannot deduce that in general, and thus requires both instances to be written out explicitly. The same thing can be observed with Eq and Ord--the latter obviously implies the former, but you still need to define an Eq instance in order to define one for Ord.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • 1
    Cool answer. Do you think that fixing those peculiarities (and possibly breaking backward-compatibility) is an option in the (near) future? Why yes/why not? Wouldn't it be worth ther effort? – gorsky Jul 04 '11 at 01:01
  • 2
    @gorsky: Backwards compatibility is the biggest obstacle, closely related to general inflexibility in how type classes interact. See [this question](http://stackoverflow.com/questions/5730270) for some discussion of why it's awkward, including my own thoughts on the matter. – C. A. McCann Jul 04 '11 at 01:13
  • 3
    `Monad` is now a subclass of `Applicative`. –  Oct 27 '15 at 08:46
9

The IO type is abstract in Haskell, so if you want to implement a general Applicative for IO you have to do it with the operations that are supported by IO. Since you can implement Applicative in terms of the Monad operations that seems like a good choice. Can you think of another way to implement it?

And yes, Applicative is in some sense less powerful than Monad.

augustss
  • 22,884
  • 5
  • 56
  • 93
  • Is `IO` an exception? It's just against my intuition - in general, simple (incomplete) graph of typeclass "dependencies" (according to Typeclassopedia article) is `Functor => Applicative => Monad`; so it is required for given type to be `Applicative` before it can be made an instance of `Monad`. Implementing `Applicative IO` in terms of `Monad IO` seems backwards to me. I'm aware of some historical issues (like `Applicative` typeclass being much newer than `Monad`) though. – gorsky Jul 03 '11 at 19:03
  • 6
    It's a matter of choice, but I think this is a good choice. You could provide operations for `Functor`, `Applicative`, and `Monad` as primitives for `IO` (since IO is abstract). Or you could just provide the `Monad` operations and implement the others in terms of `Monad`. The latter makes more sense to me, because you need to provide fewer primitive operations. It makes sense to provide as powerful primitives as possible to cut down the number. – augustss Jul 03 '11 at 19:39
  • Ah, now I get it. I thought that `Functor => Applicative => Monad` dependency forces specific order of implementing (i.e. I must first implement `Functor`, then `Applicative`, then `Monad`), but in practice it makes sense to do it differently. I stand corrected. – gorsky Jul 03 '11 at 19:53
  • 4
    @augustss: Although, there are cases where `(<*>)` can be implemented more efficiently than the generic implementation using `(>>=)`. Infinite streams being an obvious example, where the generic `ap` has to construct the Cartesian product and then take the diagonal, while `(<*>)` can simply behave like `zip`. – C. A. McCann Jul 03 '11 at 23:45
7

Isn't Applicative a less powerful concept than Monad?

Yes, and therefore whenever you have a Monad you can always make it an Applicative. You could replace IO with any other monad in your example and it would be a valid Applicative instance.

As an analogy, while a color printer may be considered more powerful than a grayscale printer, you can still use one to print a grayscale image.

Of course, one could also base a Monad instance on an Applicative and set return = pure, but you won't be able to define >>= generally. This is what Monad being more powerful means.

hammar
  • 138,522
  • 17
  • 304
  • 385
  • Yes, I'm aware of `Applicative` <-> `Monad` relationships. I was concerned that `Applicative => Monad` dependency forces me to stick to given order in implementing instances (firstly - `Applicative`, then `Monad`), but that was wrong assumption (check comments above augustss' answer). Thanks anyway, and BTW - cool printer analogy ;) – gorsky Jul 03 '11 at 19:56
3

In a perfect world every Monad would be an Applicative (so we had class Applicative a => Monad a where ...), but for historical reasons both type classes are independend. So your observation that this definition is kind of "backwards" (using the more powerful abstaction to implement the less powerful one) is correct.

Landei
  • 54,104
  • 13
  • 100
  • 195
  • Removing such quirks would be cool ;) Haskell's ecosystem seems dynamic enough. – gorsky Jul 04 '11 at 01:09
  • 1
    Starting some time last year new versions of GHC have had `class Applicative m => Monad m`, but you can still use monad functions to define the applicative instance because GHC checks that both instances actually exist before trying to compile either of them – Jeremy List Oct 28 '15 at 04:25
1

You already have perfectly good answers for older versions of GHC, but in the latest version you actually do have class Applicative m => Monad m so your question needs another answer.

In terms of GHC implementation: GHC just checks what instances are defined for a given type before it tries to compile any of them.

In terms of code semantics: class Applicative m => Monad m doesn't mean the Applicative instance has to be defined "first", just that if it hasn't been defined by the end of your program then the compiler will abort.

Jeremy List
  • 1,756
  • 9
  • 16