 # Help understanding how pure works

Want to be sure I am understanding this correctly…

``````> pure (+1) <*> [1..3]
[2,3,4]
``````
1. So `pure` is lifting `(+1)` into the context of `Applicative`.
2. `[]` is already has an instance of `Applicative` so no lifting needed there.
3. Pure is looking at the remainder of the expression `<*> [1..3]` to figure out it needs to lift `(+1)` into the context of `Applicative` rather than some other context.
4. If #3 is correct, what in `<*> [1..3]` is being looked at to determine that `Applicative` is the correct context?
1 Like

Here’s a simple way to think about it:

`pure` does different things depending on what `Applicative` you use. Here, you’re using the `[]` Applicative. The `pure` instance for the `[]` applicative is

`pure x = [x]`

Similarly, `(<*>)` does different things depending on the `Applicative`. The `(<*>)` instance for `[]` is (equivalent to):

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

If you plug in the `pure` definition to

`pure (+1) <*> [1..3]`

you get

`[(+1)] <*> [1,2,3]`

and plugging in the `<*>` definition, you get:

`[f x | f <- [(+1)], x <- [1,2,3]]`

On rereading your question, I think the point of confusion is point 3. `pure` doesn’t look into the remainder of the expression. It’s just a function applying to `(+1)`, there’s no way for it to “see” the rest of the expression. (A helpful heuristic: Haskell tends not to have “magic” ways for things to happen like that).

The higher level point here is that to understand what

`pure (+1) <*> [1..3]`

means, you have to understand how `pure` and `<*>` are defined. Those are defined by the `[]` instance for `Applicative`. Substitute those definitions in, and you’re good to go.

3 Likes

Also, in case it’s not clear, note that the bracketing is:

`(pure (+1)) <*> ([1..3])`

That’s why I say that `pure` doesn’t see anything but `(+1)`

2 Likes

Though maybe it’s important to note that the compiler does “see” the <*> [1,2,3] in order to infer which Applicative (pure (+1)) must be.

1 Like

Think about your same question but replace `Applicative` with `Functor`. `Fmap` "lifts a function into the context of `Functor`", as it were. If you had

``````> fmap (+1) [1..3]
[2,3,4]
``````

would you say "`fmap` is looking at the remainder of the expression `[1..3]` to figure out it needs to lift (+1) into the context of `Functor` rather than some other context"? No, of course not, because `fmap` always lifts functions into "the context of `Functor`", and you know that by looking at its type signature! But there’s actually some more confusion here, because it’s not really "the context of `Functor`"; let me explain.

`Applicative` is a typeclass, just like `Functor`, and so there are multiple types which have `Applicative` instances. If you have some confusion because the source you’re learning from talks about how `pure` can lift something into different contexts, what they mean is that the different types which are instances of `Applicative` are different contexts. That is, `[a]` is a different context than `Maybe a`, which is a different context than `IO a`, which is a different context than `Either String a`, and so on, and those are all `Applicatives` (and `Functors`, and `Monads` for that matter). `pure` will always “lift into an `Applicative` context”, of which there are more than one. But you can be sure that it will always be an instance of `Applicative`, because it’s guaranteed by the type signature of `pure`.

So, it just looks to me like your main confusion is in thinking that `pure` can lift something into a context other than something involving `Applicative` (which again, is a typeclass, not a type itself). `pure` will always lift into some kind of `Applicative` context, meaning, some type which has an `Applicative` instance. So it’s a subtle point about language: it’s not "the context of `Applicative`", it’s “an `Applicative` context,” of which, again, there are many.

1 Like

Now that you mention it, it is obvious I should have said compiler- thx!

Good point! I kind of knew that but I see the distinction more clearly now.

Very helpful! Speaking of the definition of the `[]` instance for `Applicative`:

``````instance Applicative [] where
{-# INLINE pure #-}
pure x    = [x]
{-# INLINE (<*>) #-}
fs <*> xs = [f x | f <- fs, x <- xs]
{-# INLINE liftA2 #-}
liftA2 f xs ys = [f x y | x <- xs, y <- ys]
{-# INLINE (*>) #-}
xs *> ys  = [y | _ <- xs, y <- ys]
``````

Can you explain the left hand side of `fs <*> xs = [f x | f <- fs, x <- xs]`, i.e. `fs <*> xs`?

1. It looks like pattern matching on an operator - is that a thing?

2. I’ll guess that `fs` and `xs` both need to be `[]`? If so, what enforces that?

3. And there is nothing beyond enforcing `[]`, i.e., you could put anything in there?

Happy to help!

Re 1: it’s just a normal function definition (but for an operator). I don’t think it’s pattern matching. For example, in Haskell, I could define a new operator like this:

``````(+++) :: Int -> Int -> Int
a +++ b = a + b + 1
``````

Does that make sense?

Re 2: it’s important to be very precise here. `fs` isn’t of type `[]` exactly, it’s of type `[a]`. Same for `xs`. There’s a few things you could mean by “what enforces that?”, but one answer is: since we’re writing the Applicative instance for `[]`, that’s what enforces it.

Re 3: no. In this line, the type of `<*>` is `[a ->b] -> [a] -> [b]`, so you could not put just anything here.

2 Likes

Note that you can also write it in prefix style:

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

Then you can just consider `(<*>)` to be the name of the function.

2 Likes

#1
I’m looking at Base.hs and don’t see a type signature there so missed that. In VS Code HLS says the type signature is (GHCi gives a parse error)

``````(<*>) :: [t -> a] -> [t] -> [a]
fs <*> xs = [f x | f <- fs, x <- xs]
``````

Interesting that `fs` & `xs` are not on the left hand side. Seems like a big hint that an operator is being defined. I have never defined an operator so just getting a feel for it now.

And if I reorder the lhs

``````(<*>) :: [t -> a] -> [t] -> [a]
(<*>) fs xs = [f x | f <- fs, x <- xs]
``````

I’m looking at the doc and in GHC code but don’t see a similar type signature. There is none near the declaration for `instance Applicative [] where` Is it there or maybe inferred?

#2 & 3 - good with those now.

I did see that and it does make it easier to understand.

Re 1. The “infix” notation (i.e. putting the operator between the two arguments, like in `3 + 4`) is totally a syntactic thing. So if confused, always just rearrange `3 + 4` to `(+) 3 4` (note that Haskell needs brackets for the `+` when it’s at front). Similarly, there’s nothing deep about a definition like `fs <*> xs = ...`: it’s just a totally normal function definition, which is why jaror recommended you rewrite it as `(<*>) fs xs`

You said that `fs` and `xs` aren’t on the left hand side. But they are! In `fs <*> xs = ...`, that’s `fs` and `xs` on the left hand side. Unless I misunderstood what you were saying.

It sounds like you’re looking for a definition of the form `(<*>) fs xs = ...` somewhere? That definition is `fs <*> xs = [f x | f <- fs, x <- xs]` (it’s identical! just two different ways of writing the same thing)

1 Like

Just to be totally clear: if I wanted to define a new function `blah`, e.g. of type `Bool -> Bool -> Bool`, I could either define it like:

``````a `blah` b = if a then b else a -- or whatever definition I wanted on the right
``````

or

``````blah a b = if a then b else a -- or whatever
``````

It’s literally just two syntaxes for exactly the same function. In the instance definition of the `[]` applicative, they happen to use the infix version (the first one above), but they could have just as well used the second.

1 Like

I wasn’t clear. I was speaking of the type signature `(<*>) :: [t -> a] -> [t] -> [a]` when I said not on the lhs.

When you said “the type of `<*>` is `[a ->b] -> [a] -> [b]` , so you could not put just anything here.” I went looking for the type signature in the doc and code but don’t see it.

Ah sorry, I misunderstood. Yeah, you can work out the signature from the signature of `<*>` given in the `Applicative` typeclass, which is:

``````class Functor f => Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f b
``````

So when `f` is specialized to `[]`, we get `(<*>) :: [] (a -> b) -> [] a -> [] b` which is just another way of writing `(<*>) :: [(a -> b)] -> [a] -> [b]`

Hope that’s useful!

1 Like