Christian Gill

Lifting const

Originally posted on

Lifting const

Yesterday I shared this gist:

And I planned to expand on it today based on some assumptions I had:

By playing around in GHCi I found out that my assumption was wrong.

you are wrong meme

So I did a bit of digging, by digging I mean Hoogling the functions and checking their sources in Hackage, to understand why.

I found some interesting things.

Let's check again how the operator (<*) works.

(<*) :: Applicative f => f a -> f b -> f a

As it is always the case in Haskell, the signature says a lot. It looks like it takes two applicatives and returns the first one.

Just 1 <* Just 2 -- Just 1

And yes, that's right. But there's more...

It does not only return the first one discarting the second. Instead it "runs" both and returns the first one. How do we know it "runs" both? Because the semantic of such applicative (in this case Maybe) are respected:

Nothing <* Just 1  -- Nothing
Just 1  <* Nothing -- Nothing

And that is the difference with const.

Just 1  `const` Just 2  -- Just 1
Just 1  `const` Nothing -- Just 1
Nothing `const` Just 1  -- Nothing

When I said that (<*) is the "lifted" version of const I didn't mean lifted in the Haskell sence, I meant lifted as in the function was used in the context of an applicative. Well I guess that's kind of what lifted means, so let's try again. What I meant is that the signature was "the same" but in the applicative context (same semantics, always returning the first argument and discarting the second).

Turns out, (<*) is actually the "lifted" version of const (in the Haskell sense). Oh, and that's exactly the implementation in base.

class Functor f => Applicative f where
    -- ...

    -- | Sequence actions, discarding the value of the second argument.
    (<*) :: f a -> f b -> f a
    (<*) = liftA2 const

See source in Hackage

So what I was wrong about? 🤔

I assumed that if you lifted const it would still behave as the good ol' const.

constA = liftA2 const

Just 1  `constA` Just 2  -- Just 1
Just 1  `constA` Nothing -- Just 1
Nothing `constA` Just 1  -- Nothing

Well, as we saw already, it doesn't. As I said it respects the semantics of the applicative.

constA = liftA2 const

Just 1  `constA` Just 2  -- Just 1
Just 1  `constA` Nothing -- Nothing
Nothing `constA` Just 1  -- Nothing

Because that is the whole point of liftA2.

* to be continued (whit a deeper look into liftA2).

P.S. Yeah I used a "lifting" 🏋️ picture in a post about lifting. Sorry.