9

According to 'Learn you a Haskell', the implementation of <*> for lists is:

fs <*> xs = [f x | f <- fs, x <- xs]

Am I mistaken, or is this sugared monadic code based on >>= ?

As far as I understand, it should be possible to implement <*> only using fmap, as it is the case with applicative Maybe.

How could <*> for lists be implemented only using fmap? (and possibly without concat-ing things?)

BTW, a few pages later I see the same issue with regards to the implementation of <*> for applicative IO.

Marco Faustinelli
  • 3,734
  • 5
  • 30
  • 49

4 Answers4

16

No, this is not sugared monadic code based on >>=. If it were, the definition of >>= in the Monad [] instance would be circular.

instance Monad []  where
    {-# INLINE (>>=) #-}
    xs >>= f             = [y | x <- xs, y <- f x]
    ...

The list comprehensions are syntactic sugar for let, if, and concatMap. From the Haskell Report:

[  e | b,          Q ] = if  b     then [ e | Q ] else []
[  e | let decls,  Q ] = let decls in   [ e | Q ]
[  e | p <- l,     Q ] = let ok p  =    [ e | Q ]
                             ok _  =    []
                         in concatMap ok l

The Monad [] instance is easy to define in terms of concatMap, but concatMap was defined in GHC.List (and is now possibly defined in Data.Foldable). Neither GHC.List nor Data.Foldable is imported into GHC.Base, so defining the Monad instance for lists in GHC.Base in terms of concatMap is impossible:

instance Monad [] where
    (>>=) = flip concatMap -- concatMap isn't imported

Defining these instances in terms of list comprehension gets around needing to import the module containing concatMap to reuse it defining >>=.

In GHC there are two implementations of list comprehensions. One rewrites them in terms of the GHC.Base build and foldr similar to the Data.Foldable concatMap. The other implementation generates recursive functions in place of concatMap as described by Wadler.

Cirdec
  • 24,019
  • 2
  • 50
  • 100
  • 1
    Quite interesting! Do you know how this <- operator is implemented? – Marco Faustinelli Mar 30 '15 at 15:15
  • 1
    @Muzietto I think this is specialcased as list comprehension. I.e. compiler magic – Bartek Banachewicz Mar 30 '15 at 15:25
  • 1
    @Muzietto List comprehensions are syntactic sugar for `let`, `if`, and `concatMap`. `concatMap` doesn't exist where the `Monad` and `Applicative` instances are defined in `GHC.Base`; I added an explanation of both of these to my answer. – Cirdec Mar 30 '15 at 15:37
  • @BartekBanachewicz Monad comprehensions are only used when the [`MonadComprehensions` extension](https://downloads.haskell.org/~ghc/7.6.3/docs/html/users_guide/syntax-extns.html) (7.3.11) is enabled and it isn't enabled in `GHC.Base`. No compiler magic is necessary, – Cirdec Mar 30 '15 at 15:46
  • @dfeuer I added a note about how list comprehensions are actually implemented. – Cirdec Mar 30 '15 at 23:38
10

There are lots of cases where the Applicative instance is satisfied by monadic functions, I've seen

instance Applicative MyMonadThatIsAlreadyDefined where
    pure = return
    (<*>) = ap

Also, <*> can't be written using only fmap, at least not in general. That's the point of <*>. Try writing <*> in terms of just fmap, I'll be very surprised if you manage it (in such a way that is well behaved and follows the applicative laws). Remember that the chain is

Functor > Applicative > Monad

Where > can be thought of as superset. This is saying that the set of all functors contains the set of all applicatives, which contains the set of all monads. If you have a monad, then you have all the tools needed to use it as an applicative and as a functor. There are types that are functorial but not applicative, and types that are applicative by not monadic. I see no problem in defining the applicative instance in this manner.

bheklilr
  • 53,530
  • 6
  • 107
  • 163
  • 2
    I see what you mean, but my point is about building applicatives 'from the ground up'. Every time I see do-notation, my first thought is: "was it REALLY necessary?" – Marco Faustinelli Mar 31 '15 at 07:27
8

Am I mistaken, or is this sugared monadic code based on >>= ?

I don't know if >>= is actually used to de-sugar list comprehensions (but see Cirdec's answer for evidence that it's not), but it's actually completely legal to define <*> in terms of >>=. In mathematical terms, every Monad instance induces a unique corresponding Applicative instance, in the sense that

instance Applicative F where
    pure = return
    af <*> ax = af >>= \ f -> ax >>= \ x -> return (f x)

is a law-abiding Applicative instance whenever F has a law-abiding Monad instance.

There's an analogy here from mathematics, if you're familiar with it:

Similarly, for every monad structure there is a compatible applicative structure, and for every applicative structure there is a compatible fmap (fmap f ax = pure f <*> ax), but the reverse implications do not hold.

As far as I understand, it should be possible to implement <*> only using fmap, as it is the case with applicative Maybe.

I don't understand what you mean here. fmap is certainly not enough to define <*>, or every Functor would be an Applicative (well, an Apply).

Jonathan Cast
  • 4,569
  • 19
  • 34
  • I was fooled by the Maybe case, but I clearly have still to understand well these concepts. Your answer is good food for thought. BTW, why do you mention 'Apply'? I know about 'idioms', but didn't know about yet another name for applicative functors. – Marco Faustinelli Mar 30 '15 at 16:04
  • @Muzietto: I can't find it now, but I saw somewhere a definition of an `Apply` class with a `<*>` method but no `unit`, which I believe is useful for certain cases involving lenses. Anyway, the point is that `Applicative` is `unit` + `<*>`, so defining just `<*>` isn't good enough to make your type an `Applicative`. – Jonathan Cast Mar 30 '15 at 18:08
  • @user3577858: I do mean `pure`. Too much Global Script on the brain :) – Jonathan Cast Mar 31 '15 at 16:27
3

This answer is complementary to the ones already given, and focusses only on a part of the question:

As far as I understand, it should be possible to implement <*> for lists only using fmap, as it is the case with applicative Maybe. How?

You seem to refer to this implementation:

instance Applicative Maybe where
    pure = Just
    Nothing  <*> _         = Nothing
    (Just f) <*> something = fmap f something

Well, yes, we can do that for lists as well - using nothing but fmap, pattern matching and constructors:

instance Applicative [] where
    pure = []
    []     <*> _         = []
    (f:fs) <*> something = fmap f something ++ (fs <*> something)
      where
        []     ++ yy = ys
        (x:xs) ++ ys = x : (xs++ys)

Admittedly, this does require some kind of concatenation, as lists are a more complex type than Maybes. There are other possible applicative instances for lists that would require less code than this "everything-with-everything" behaviour, but those are not compatible to the default monad instance (which is the common expectation).

Of course, monad notation does simplify this dramatically:

instance Monad m => Applicative m where
    pure = return
    mf <*> something = mf >>= (\f -> fmap f something) -- shorter: (`fmap` something)

…which works for both Maybe and [] as m.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • You hit the nail on its head. I appreciated A LOT all the info about Haskell implementation, but this is exactly what I was looking for. – Marco Faustinelli Mar 31 '15 at 07:22