7

Note: Still learning Haskell, not reached Monoids, studying Applicative Functors.

I saw this implementation and I am not entirely clear on it:

instance Applicative ((->) r) where  
    pure x = (\_ -> x)  
    f <*> g = \x -> f x (g x)

This is what I think:

Now I get what (->) r a is but I am not entirely clear on what it does. This is what I think it does:

  • It takes two arguments (types) and returns a function from first to second type, i.e. -> Int Int returns Int -> Int which is of kind * but what about the definition?

Now ((->) r is goind to be defined as an applicative functor that is a functor containing r -> x. So we have two cases:

  • [pure x = (\_ -> x)]: For pure we need to wrap a given -> r x into some functor but we are defining a function that just ignores input data type and returns data type x. example pure (*2) should return Maybe (Int -> Int) or some other functor binding the given function -> r x
  • [f <*> g = \x -> f x (g x)]: For two given functions whose first argument (data type) is same as r we wish to <*> or fmap one onto another, i.e. (((->) r) f) <*> (((->) r) g) or (r->f) <*> (r->g) but isn't <*> same as (.) for functions but for (.) they should be something like a->b and b->c? example (*2) <*> (+7) of the form (Int -> Int) <*> (Int -> Int)? (which comes out to be invalid but (+) <$> (*2) <*> (+7) is valid?)

Note: for (+) <$> (*2) <*> (+7) I think it is ((+) <$> (*2)) <*> (+7) which is + (*2 x) y fmap-ed onto (+7) but how is this achieved?

What am I getting it wrong?

2
  • instance Applicative ((->) x) where pure v x = v ; (f <*> g) x = (f x) $ (g x) should be much clearer.
    – Will Ness
    Oct 28, 2017 at 20:25
  • "but isn't <*> [the] same as (.) for functions?" no, that's <$>, so (+) <$> (*2) <*> (+7) == (\x -> (((+).(*2) x) $ ((+7) x)).
    – Will Ness
    Oct 29, 2017 at 9:40

3 Answers 3

7

The type (->) r a is just an alias for r -> a, nothing more. Its kind is * (“type”).

The type constructor (->) r can be thought of as r -> ___ where r is already known but ___ is to be filled in later. Its kind is * -> * (“type constructor”).

The (->) r type constructor is itself is parametrized by the input type r. To reduce the risk confusion, it might help to fix r to something concrete. So for the rest of this answer, I’ll choose r = Int. The explanation below will work for any choice of r.

Both applicative functors and functors classify type constructors, not types:

  • We say a [___] (list constructor) is a functor, Int -> ___ (functions that expect an integer) is a functor, IO ___ is a functor, Maybe ___ is a functor, etc.
  • We do not say [Int] (list of integers) is a functor, nor do we say Int -> String (functions expect an integer and return a string) is a functor, nor IO (), nor Maybe String, etc. (Of course, in informal writing people do say this out of sloppiness, but while learning it’s important to keep this distinction in mind.)

pure :: a ->        f a     -- general type, where f is Applicative
pure :: a -> (->) Int a     -- specialized type for f = ((->) Int)
pure :: a ->  (Int -> a)    -- just another way to write the above
pure x = (\_ -> x)

The pure function creates a function that will return a, ignoring whatever Int it will receive. It provides a way to “wrap” things into an applicative functor.

As a concrete example, if you write pure "hi", this creates a function \_ -> "hi" that always returns "hi" no matter what input it gets. To get your "hi" back, you apply the result to any integer, “unwrapping” the applicative functor:

>>> (pure "hi") 42
"hi"

(<*>) ::        f (a -> b)  ->        f a  ->        f b     -- general type, where f is Applicative
(<*>) :: (->) Int (a -> b)  -> (->) Int a  -> (->) Int b     -- specialized type for f = ((->) Int)
(<*>) ::  (Int -> (a -> b)) ->  (Int -> a) ->  (Int -> b)    -- just another way to write the above
f <*> g = \x -> f x (g x)

The <*> function takes two functions:

  • The first function f will return a function a -> b in exchange for an Int.
  • The second function g will return some value a in exchange for an Int.

The goal of <*> is to create a new function that produces some value b in exchange for an Int. There is one “obvious” way to play this game: when you do get that Int later in the future, you send it to both f and g and they will in exchange give you a -> b and a. Then you can apply the a -> b to a to get b, which achieves the goal.

Here’s a concrete example: pure (+) <*> pure 2.0 <*> pure 3.0. This expression creates a function that always yields 5.0 in exchange for whatever Int you give. As usual you can “unwrap” it by applying it to some Int:

>>> (pure (+) <*> pure 2.0 <*> pure 3.0) 42
5.0

But so far, none of these examples were very interesting, because they only “wrap” things using pure, which always ignores the argument. Here’s a more interesting one:

>>> let get input = input
>>> (pure (-) <*> get <*> pure 7) 42
35
>>> (pure (-) <*> get <*> pure 7) 10
3

Compared to the previous examples, the get function is rather unusual in that it does actually inspect the Int argument, returning exactly what it sees. This means the result of this chain of computations differs depending on what argument it receives at the very end.

  pure (-) <*> get <*> pure 7
≡ (\input -> (-)) <*> (\input -> input) <*> (\input -> 7)
≡ (\input -> (-) input) <*> (\input -> 7)
≡ (\input -> (-) input 7)
≡ (\input -> input - 7)

In fact, if you just look at the original applicative expression and squint a bit to ignore the pure and <*>, you can read it directly as

  (-) INPUT 7
≡ INPUT - 7

where INPUT represents the future Int value.

Now consider your example:

  (+) <$> (*2) <*> (+7)
≡ (+) <$> (\input -> input * 2) <*> (\input -> input + 7)
≡ (\input -> (+) (input * 2)) <*> (\input -> input + 7)
≡ (\input -> (+) (input * 2) (input + 7))
≡ (\input -> (input * 2) + (input + 7))

Informally this describes the following computation:

  (+) (INPUT * 2) (INPUT + 7)
≡ (INPUT * 2) + (INPUT + 7)

For example, if the input is 42, you should expect 133 as the result:

>>> ((+) <$> (*2) <*> (+7)) 42
133

The purpose of the ((->) Int) applicative functor to allow the programmer to separate the logic that depends on the input argument from the actual code that supplies the concrete value. In other words, in real code you would likely be building a rather large and complicated applicative expression, without knowing what the input will be. You would not be unwrapping the applicative value right away, but likely some far-away place in the code (perhaps in another project entirely!).

0
5

Let's use some equational reasoning.

Our functor f is (->) r, so f a is (->) r a which is just r -> a.

pure :: a -> f a, so in this case pure :: a -> (r -> a).

<*> :: f (a -> b) -> f a -> f b, so we get <*> :: r -> (a -> b) -> (r -> a) -> (r -> b).

fmap :: (a -> b) -> f a -> f b, so we get fmap :: (a -> b) -> (r -> a) -> (r -> b).


If you look at what fmap does, it takes a single-argument function and "puts it in" the functor. What about functions of two or more arguments? That's why we have applicative functors.

Say we have a function g :: a -> b -> c -> d. We want to apply it to arguments x :: f a, y :: f b, and z :: f c.

So we do pure g <*> x <*> y <*> z.

pure g :: f (a -> b -> c -> d). Let's rewrite it as f (a -> (b -> c -> d)), since <*> expects an argument of type f (a -> b).

pure g <*> x :: f (b -> c -> d). We can rewrite it as f (b -> (c -> d)) for clarity. You can see where this is going, and you end up with a result of type f d.


We can do the same thing for fmap, so fmap f x = pure f <*> x. Since we're going to start all these chains with the same thing, we can even abbreviate fmap as <$>, so things look like g <$> x <*> y <*> z.


but isn't <*> same as (.)

No, and you can tell by looking at the type signature. (.) :: (b -> c) -> (a -> b) -> (a -> c), which does match our type signature for fmap above, except for shuffling around the letters.


(*2) <*> (+7)

This doesn't typecheck, as you noted. Why?

Let's say f = (*2) and g = (+7), and both are of type Int -> Int.

<*> :: (r -> (a -> b)) -> (r -> a) -> (r -> b).

But we know that r = Int, so <*> :: (Int -> (a -> b)) -> (Int -> a) -> (Int -> b). Our second argument tells us that a = Int, which is fine, but our first argument is Int -> Int and doesn't have enough arguments.


I think it is ((+) <$> (*2)) <*> (+7) which is + (*2 x) y fmap-ed onto (+7) but how is this achieved?

((+) <$> (*2)) <*> (+7) means \x -> (x*2) + (x+7). If you look at the definition of <*>, you see that x gets fed into both f and g.

4

One thing you are missing is that the "arrow operator" (->) works on types not values. (In other words it's a type constructor)

(->) Int Int is identical to Int -> Int which is a familiar function type you know and love. It is not a function; it's a function type.

Now ((->) r is goind to be defined as an applicative functor that is a functor containing r -> x. So we have two cases:

I'm not sure what you mean by "containing" here. This is defining that (->) r aka r -> is a functor. It's not defining r -> x to be a functor. And a functor doesn't "contain" anything. (Well, List is functor that contains things, but other functors don't have to)

For pure we need to wrap a given -> r x into some functor

No. For pure we need to wrap a given x into a -> r x a.k.a. r -> x (which is a functor -> r applied to x).

but we are defining a function that just ignores input data type and returns data type x.

Indeed. That is how you convert an x into a -> r x a.k.a. r -> x.

example pure (*2) should return Maybe (Int -> Int)

It does - with the Maybe functor. pure (*2) :: (Maybe (Int -> Int)) would do that - but it will not return Maybe (Int -> Int), because that's a type; it will return Just (*2).

f <*> g = \x -> f x (g x): For two given functions whose first argument (data type) is same as r we wish to <*> or fmap one onto another, i.e. (((->) r) f) <*> (((->) r) g) or (r->f) <*> (r->g)

(<*>) is not the same as fmap. For this functor fmap is defined to be the same as (.), and (<*>) isn't.

but isn't <*> same as (.) for functions

No.

but for (.) they should be something like a->b and b->c?

Right ... and for (<*>) they aren't like that, so clearly (<*>) is different from (.)

example (*2) <*> (+7) of the form (Int -> Int) <*> (Int -> Int)?

In the (->) r applicative functor, <*> can be thought of as "deferred application".

(->) r is similar to the Reader r monad - it represents computations that also have access to some "environment value". A "value" of type a that depends on the environment is represented as a (->) r a a.k.a. r -> a (cf. Reader r a). In other words, a r -> a is an "a wrapped in the functor r ->")

Now we can compare things to their equivalent forms "outside" the functor, to see how things work "inside".

Inside the functor, (*2) and (+7) are values (they're twice the environment value, and the environment value plus seven, respectively).

You can't apply a value to another value, so this doesn't work. ((*2) <*> (+7) is analogous to 4 9 for example)

(which comes out to be invalid but (+) <$> (*2) <*> (+7) is valid?)

(+) <$> (*2) <*> (+7) applies the function (+) to these values - yielding another "value wrapped in a functor" which will be three times the environment value plus seven. The result is equivalent to \x -> (x*2) + (x+7).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.