Revision as of 23:32, 13 September 2013

Haskell calls a couple of historical accidents its own. While some of them, such as the "number classes" hierarchy, can be justified by pragmatism or lack of a strictly better suggestion, there is one thing that stands out as, well, not that: Applicative not being a superclass of Monad.

The topic has been discussed multiple times in the past (cf. link section at the very end). This article was updated to describe the current, and very likely to succeed, Haskell 2014 Applicative => Monad proposal (AMP).

1 Proposal contents

Applicative becomes a superclass of Monad, and is added to the Prelude.

Alternative becomes a superclass of MonadPlus (in addition to Monad, of course).

join

is promoted into the Monad typeclass.

The general rationale behind these changes:

Break as little code as possible. For example, do not move

return

to Applicative and remove

pure

. Instead, leave

return

in Monad, and give it

pure

as default implementation.

Change only things that are closely related to the proposal. For example, using

join

in a monad definition requires it to be a functor, so it goes hand in hand with the AMP. On the other hand, removing

fail

has nothing to do with what we're trying to accomplish.

2 Future-proofing current code

GHC 7.8 will issue two types of warnings in order to encourage wide-scale code fixing. The following describes how to get rid of them, and as a result ensuring your code builds both now and after the AMP is finished.

2.1 Missing superclasses

(Warnings of the type "Warning: X is an instance of C, but not D")

Add Applicative/Functor instances for all your Monads. You can simply derive these instances from the Monad by adding the following code:

2.2 Future Prelude names

"The name X clashes with a future Prelude name" - Prelude will export functions called

<*>

,

join

and

pure

, so if a module defines its own versions of them, there will be name clashes. There are multiple ways of getting rid of this type of warning.

Change your code to not define functions named

<*>

,

join

or

pure

.

Import Prelude definitions you need explicitly. For example,

importPrelude(map,(+))

would not import

join

, so no warning is issued as the module is compatible with the Prelude exporting

join

. Note: due to how GHC handles

hiding

statements, it impossible to make

importPreludehiding(join)

not create a warning when the hidden name is not actually exported. In other words, you can't use

hiding

.

As a last resort, you can use the compiler option -fno-warn-amp, which silences all AMP warnings. Be careful with this though, because once 7.10 hits that module will break if the AMP issues haven't been fixed properly.

3 Discussion and consequences

3.1 It's the right thing to do™

Math. You've all heard this one, it's good and compelling so I don't need to spell it out.

3.2 Redundant functions

pure

and

return

do the same thing.

>>

and

*>

are identical.

liftM

and

liftA

are

fmap

. The

liftM*

are

liftA*

,

<*>

is

ap

.

Prelude's

sequence

requres

Monad

right now, while

Applicative

is sufficient to implement it. The more general version of this issue is captured by

provides a semi-automatic way to using Functor/Applicative/Alternative functions for Monad/MonadPlus instances as a makeshift patch.

That very much violates the "don't repeat yourself" principle, and even more so it forces the programmer to repeat himself to achieve maximal generality. It may be too late to take all redundancies out, but at least we can prevent new ones from being created.

(Note that it is not proposed to remove any functions for compatibility reasons. Maybe some of them can be phased out in the long run, but that's beyond scope here.)

3.3 Using Functor/Applicative functions in monadic code

Whenever there's Monad code, you can use Functor/Applicative functions, without introducing an additional constraint. Keep in mind that "Functor/Applicative functions" does not only include what their typeclasses define but many more, for example

void

,

(<$>)

,

(<**>)

.
Even if you think you have monadic code, strictly using the least restrictive functions may result in something that requires only Applicative. This is similar to writing a function that needs

Int

, but it turns out any

Integral

will do - more polymorphism for free.

3.4 Compatibility issues

These are the kinds of issues to be expected:

Monads lacking Functor or Applicative instances. This is easily fixable by either setting

fmap= liftM

,

pure =return

and

(<*>)= ap

, although more efficient implementations may exist, or by moving an already existing definition from

Control.Applicative

to the appropriate module.

This one is specific to building GHC: importing

Control.Monad/Applicative

introduces a circular module dependency. In this case, one can rely on handwritten implementations of the desired function, e.g.

ap f x = f >>=...

.

Libraries using their own

(<*>)

. This one is potentially the most laborious consequence. For building GHC though, this only concerns Hoopl, and a handful of renames.

3.5 Beginner friendliness

How often did you say ...

"A Monad is always an Applicative but due to historical reasons it's not but you can easily verify it by setting

pure =return

and

(<*>)= ap

"

"

liftM

is

fmap

but not really." - "So when should I use

fmap

and when

liftM

?" - *sigh*

With the new hierarchy, the answer would *always* be "use the least restrictive one".

4 Applying the AMP to GHC and then Haskell in practice

Proposed is a gradual introduction of the AMP in three phases:

4.1 Current stage: Prepare GHC

Using a GHC fork with the full patch applied, find and fix all compilation errors introduced by the change by adding Functor/Applicative instances for all Monads.

According to SPJ, adding an ad-hoc warning of sorts "Monad without Applicative detected" is not a problem, which will be crucial for the next phase. More specifically, issue a warning if:

Monad without Applicative

MonadPlus without Alternative

One of

<*>

,

pure

,

join

is defined in a different context to avoid naming conflicts, as these functions will go into the Prelude

4.2 Prepare Hackage

The warning just mentioned will hint to all authors that they should fix (or help others fix) the non-complying packages. This will ideally lead to libraries eventually adding Applicative instances, and changing their APIs if they redefine operators like

<*>

.

After enough time has passed by so libraries adapted to the circumstances, move on to the next phase.

4.3 Apply the proposal

Once Hackage is prepared, applying the changes to the Base package is painless. However, this is not primarily a GHC, but a Haskell change. The previous steps were basically preparing the landscape, and when we've (hopefully) found out that it is a good idea to go through with it, it can be proposed to go into the Report. If we make it this far, the AMP should pass quite easily.

5 Previous proposals

Early 2011: GHC ticket – changes similar to this proposal, but closed as "not GHC, but Haskell". See here for the associated discussion.