1

In shool, I was task to write a function which appends Numbers to the left side of a list, if they are even. The type-signature was given as:

appendIfEven :: (Applicative f, Monoid (f a), Integral a) => a -> f a -> f a

My answer was the following piece of code

appendIfEven :: (Applicative f, Monoid (f a), Integral a) => a -> f a -> f a
appendIfEven x ms = if x `mod` 2 == 0 then mempty x `mappend` ms else ms

Haskell can compile my code, but it does not work properly. After some experimenting, I switched mempty to pure :

appendIfEven :: (Applicative f, Monoid (f a), Integral a) => a -> f a -> f a
appendIfEven x ms = if x `mod` 2 == 0 then pure x `mappend` ms else ms

Which works fin. But why ? shouldn't pure == mempty ? (In this context) It seems to be not important for my Tutor. However I would really like to understand a bit more about Haskell and where I am wrong ....

Felix Hohnstein
  • 459
  • 5
  • 13
  • 2
    Well they have different types, for one. How could they be the same thing if they have different types? – user253751 Oct 01 '20 at 12:23
  • 1
    It compiled because `a -> b` has a `Monoid` instance as long as `b` is also a `Monoid`, in which case `mempty = const mempty`. – chepner Oct 01 '20 at 12:28
  • @user253751 `a -> b` has a `Monoid` instance, in which case `mempty` itself is also a function. – chepner Oct 01 '20 at 12:30

2 Answers2

5

You inadvertently used an unexpected monoid, which incidentally made your code compile.

When you wrote mempty x `mappend` ms, the ms value has type f a, which is a monoid. This forces mempty x to have the same type, since mappend requires two arguments of the same type. Hence we must have

mempty x :: f a

which implies, since x :: a,

mempty :: a -> f a

Weird, right?

Well, it happens that there is an instance in the libraries

instance Monoid t => Monoid (u -> t) where
   mempty = \_ -> mempty
   ...

which you inadvertently used passing x to mempty, since t can be f a, and u can be a in the instance above. Haskell resolved the monoid constraint so to use this instance.

This also means that

mempty x `mappend` ms

is the same as

(\_ -> mempty) x `mappend` ms  -- this is the mempty for (f a), instead!

which is the same as

mempty `mappend` ms   -- this is the mempty for (f a), instead!

which is the same as

ms

Hence, your code completely ignores the x argument.

By contrast pure x does depend on x, in the general case, so the end result can be very different.

chi
  • 111,837
  • 3
  • 133
  • 218
2

Consider applying this to lists.

mempty == []

pure 5 = [5]

Those don't look very similar to me. In particular, mempty x ought to be ill-typed.

Basically mempty gives you an empty thing, whereas pure x gives you a thing with an x in it.

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 2
    `mempty` is defined for `Monoid b => a -> b` to be `const mempty`. – chepner Oct 01 '20 at 12:28
  • 1
    @chepner Yeah, I had a feeling that the compiler doesn't complain because a suitable instance either does exist or theoretically could be made to exist. Still doesn't make it a sensible expression (unless you really are using such an instance). – MathematicalOrchid Oct 01 '20 at 12:31
  • 1
    In proper context, it can make sense to use `mempty` as a function. Instead, IMO, it makes no sense to immediately apply it as in `mempty x` since it's equivalent (in the same context) to `mempty`. – chi Oct 01 '20 at 12:50
  • the functions instance is what makes possible the famous trick `comparing snd <> comparing fst`, with `(f <> g) x y = (f x <> g x) y = f x y <> g x y`. – Will Ness Oct 05 '20 at 13:46