Chad - 8 months ago 25

Javascript Question

Given the following:

`var average = R.lift(R.divide)(R.sum, R.length)`

How come this works as a pointfree implementation of

`average`

`R.sum`

`R.length`

`R.divide`

`R.sum`

`R.length`

`var sum3 = R.curry(function(a, b, c) {return a + b + c;});`

R.lift(sum3)(xs)(ys)(zs)

In the above case the values in

`xs`

`ys`

`zs`

Expounding further, I understand that applying a lifted function is like using

`R.ap`

`R.ap(R.ap(R.ap([tern], [1, 2, 3]), [2, 4, 6]), [3, 6, 8])`

R.lift(tern)([1, 2, 3], [2, 4, 6], [3, 6, 8])

Checking the documentation it says:

"lifts" a function of arity > 1 so that it may "map over" a list, Function or other object that satisfies the FantasyLand Apply spec.

And that doesn't seem like a very useful description at least for me. I'm trying to build an intuition regarding the usage of

`lift`

Answer

The first cool thing is that `a -> b`

can support `map`

. Yes, *functions* are functors!

Let's consider the type of `map`

:

```
map :: Functor f => (b -> c) -> f b -> f c
```

Let's replace `Functor f => f`

with `Array`

to give us a concrete type:

```
map :: (b -> c) -> Array b -> Array c
```

Let's replace `Functor f => f`

with `Maybe`

this time:

```
map :: (b -> c) -> Maybe b -> Maybe c
```

The correlation is clear. Let's replace `Functor f => f`

with `Either a`

, to test a binary type:

```
map :: (b -> c) -> Either a b -> Either a c
```

We often represent the type of a function from `a`

to `b`

as `a -> b`

, but that's really just sugar for `Function a b`

. Let's use the long form and replace `Either`

in the signature above with `Function`

:

```
map :: (b -> c) -> Function a b -> Function a c
```

So, mapping over a function gives us a function which will apply the `b -> c`

function to the original function's return value. We could rewrite the signature using the `a -> b`

sugar:

```
map :: (b -> c) -> (a -> b) -> (a -> c)
```

Notice anything? What is the type of `compose`

?

```
compose :: (b -> c) -> (a -> b) -> a -> c
```

So `compose`

is just `map`

specialized to the Function type!

The second cool thing is that `a -> b`

can support `ap`

. Functions are also *applicative* functors! These are known as Applys in the Fantasy Land spec.

Let's consider the type of `ap`

:

```
ap :: Apply f => f (b -> c) -> f b -> f c
```

Let's replace `Apply f => f`

with `Array`

:

```
ap :: Array (b -> c) -> Array b -> Array c
```

Now, with `Either a`

:

```
ap :: Either a (b -> c) -> Either a b -> Either a c
```

Now, with `Function a`

:

```
ap :: Function a (b -> c) -> Function a b -> Function a c
```

What is `Function a (b -> c)`

? It's a bit confusing because we're mixing the two styles, but it's a function that takes a value of type `a`

and returns a function from `b`

to `c`

. Let's rewrite using the `a -> b`

style:

```
ap :: (a -> b -> c) -> (a -> b) -> (a -> c)
```

Any type which supports `map`

and `ap`

can be "lifted". Let's take a look at `lift2`

:

```
lift2 :: Apply f => (b -> c -> d) -> f b -> f c -> f d
```

Remember that `Function a`

satisfies the requirements of Apply, so we can replace `Apply f => f`

with `Function a`

:

```
lift2 :: (b -> c -> d) -> Function a b -> Function a c -> Function a d
```

Which is more clearly written:

```
lift2 :: (b -> c -> d) -> (a -> b) -> (a -> c) -> (a -> d)
```

Let's revisit your initial expression:

```
// average :: Number -> Number
const average = lift2(divide, sum, length);
```

What does `average([6, 7, 8])`

do? The `a`

(`[6, 7, 8]`

) is given to the `a -> b`

function (`sum`

), producing a `b`

(`21`

). The `a`

is also given to the `a -> c`

function (`length`

), producing a `c`

(`3`

). Now that we have a `b`

and a `c`

we can feed them to the `b -> c -> d`

function (`divide`

) to produce a `d`

(`7`

), which is the final result.

So, because the Function type can support `map`

and `ap`

, we get `converge`

at no cost (via `lift`

, `lift2`

, and `lift3`

). I'd actually like to remove `converge`

from Ramda as it isn't necessary.

Note that I intentionally avoided using `R.lift`

in this answer. It has a meaningless type signature and complex implementation due to the decision to support functions of any arity. Sanctuary's arity-specific lifting functions, on the other hand, have clear type signatures and trivial implementations.