Lifting const
Originally posted on dev.to/gillchristian.
Yesterday I shared this gist:
[twitter https://twitter.com/gillchristian/status/1175878924926095360]
And I planned to expand on it today based on some assumptions I had:
[twitter https://twitter.com/gillchristian/status/1176126201234169860]
By playing around in GHCi I found out that my assumption was wrong.
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
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.