Given the following:
var average = R.lift(R.divide)(R.sum, R.length)
How come this works as a pointfree implementation of average? I don't understand why I can pass R.sum and R.length when they are functions and therefore, I cannot map the lifted R.divide over the functions R.sum and R.length unlike in the following example:
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 and zs are summed in a non deterministic context, in which case the lifted function is applied to the values in the given computational context.
Expounding further, I understand that applying a lifted function is like using R.ap consecutively to each argument. Both lines evaluate to the same output:
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. I hope someone can provide that.
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.
As I have hard time understanding the same issue, I decided to take a peek from Ramda's source code. Will write a blogpost about this in the future. Meanwhile—I made a commented gist how Ramda's lift work step by step.
from: https://gist.github.com/philipyoungg/a0ab1efff1a9a4e486802a8fb0145d9e
// Let's make an example function that takes an object and return itself.
// 1. Ramda's lift level
lift(zipObj)(keys, values)({a: 1}) // returns {a: 1}
// this is how lift works in the background
module.exports = _curry2(function liftN(arity, fn) {
var lifted = curryN(arity, fn);
return curryN(arity, function() {
return _reduce(ap, map(lifted, arguments[0]), Array.prototype.slice.call(arguments, 1)); // found it. let's convert no 1 to no 2
});
});
// 2. Ramda's reduce level
reduce(ap, map(zipObj, keys))([values])
// first argument is the function, second argument is initial value, and the last one is lists of arguments. If you don't understand how reduce works, there's a plenty of resources on the internet
// 3. Ramda's ap level
ap(map(zipObj, keys), values)
// how ap works in the background
module.exports = _curry2(function ap(applicative, fn) {
return (
typeof applicative.ap === 'function' ?
applicative.ap(fn) :
typeof applicative === 'function' ? //
function(x) { return applicative(x)(fn(x)); } : // because the first argument is a function, ap return this.
// else
_reduce(function(acc, f) { return _concat(acc, map(f, fn)); }, [], applicative)
);
});
// 4. Voilà. Here's the final result.
map(zipObj, keys)({a: 1})(values({a: 1}))
// Hope it helps you and everyone else!
Related
I've kind of grasped some knowledge on functional programming but can't really wrap my head around this function programming block of code. I didn't really knew where I should ask something like this I couldn't figure out so asked it here. So I would really appreciate if someone will help me understand what this higher order function, or a monads example doing?
P.S this code is from composing software book by Eric Elliot
const f = n => n+1;
const g = n => n*2;
This composeM functions is made to compose and map or over numbers of functions? I know reduce but really have no idea how this function should be working.
const composeM = (...mps) => mps.reduce((f, g) => x => g(x).map(f));
const h = composeM(f,g);
h(20)
Then, the function composeM was more generalized by doing:
const compose = methods => (...mps) => mps.reduce((f, g) => x => g(x)[method](f));
Then, I could create composedPromises or composedMaps like
const composePromises = compose("then")(f,g);
How is the g(x)[method](f) even working? it should be g(x).then(f).
Update above map composeM function not working
const f = n => Promise.resolve( n+1 );
const g = n => Promise.resolve( n*2 );
const composePromises = (...mps) => mps.reduce((f, g) => x => g(x).then(f))
const h = composePromises(f, g)
h(20)
Consider function composition, which has the following type signature.
// compose :: (b -> c) -- The 1st argument is a function from b to c.
// -> (a -> b) -- The 2nd argument is a function from a to b.
// -> (a -> c) -- The final result is a function from a to c.
// +-----------------b -> c
// | +---------a -> b
// | | +-- a
// | | |
const compose = (f, g) => x => f(g(x));
// |_____|
// |
// c
The composeP function is similar to the compose function, except it composes functions that return promises.
// composeP :: (b -> Promise c) -- The 1st argument is a function from b to promise of c.
// -> (a -> Promise b) -- The 2nd argument is a function from a to promise of b.
// -> (a -> Promise c) -- The final result is a function from a to promise of c.
// +-------------------------b -> Promise c
// | +-------- a -> Promise b
// | | +-- a
// | | |
const composeP = (f, g) => x => g(x).then(f);
// |__________|
// |
// Promise c
Remember that the then method applies the callback function to the value of the promise. If we replace .then with [method] where method is the name of the bind function of a specific monad, then we can compose functions that produces values of that monad.
For example, .flatMap is the bind function of arrays. Hence, we can compose functions that return arrays as follows.
// composeA :: (b -> Array c) -- The 1st argument is a function from b to array of c.
// -> (a -> Array b) -- The 2nd argument is a function from a to array of b.
// -> (a -> Array c) -- The final result is a function from a to array of c.
const composeA = (f, g) => x => g(x).flatMap(f);
// f :: Bool -> Array String
const f = x => x ? ["yes"] : ["no"];
// g :: Int -> Array String
const g = n => [n <= 0, n >= 0];
// h :: Int -> Array String
const h = composeA(f, g);
console.log(h(-1)); // ["yes", "no"]
console.log(h(0)); // ["yes", "yes"]
console.log(h(1)); // ["no", "yes"]
That was a very contrived example but it got the point across.
Anyway, the generic compose function composes monads.
const compose = method => (f, g) => x => g(x)[method](f);
const composeP = compose("then");
const composeA = compose("flatMap");
Finally, the various monad compose functions only compose two functions at a time. We can use reduce to compose several of them at once. Hope that helps.
Is the following attempt at the Hindley-Milner Type Signature for the compose function correct?
// compose :: (f -> [f]) -> (f -> f -> f) -> [f] -> f
const compose = (...fns) => fns.reduce((f,g) => (...args) => f(g(...args)));
No, that's not correct. Your compose function takes an array of functions as input, and produces one (composed) function as the output, so that signature is clearly wrong. On the other hand, I don't think it's possible to write that function using the Hindley-Milner type system unless you make the assumption that all of the functions in fns are unary functions of the same type: a -> a (i.e. an array of endomorphisms).
compose :: [a -> a] -> (a -> a)
JavaScript is dynamically typed, so it actually allows each function in fns to be a different type (JS doesn't require arrays to be homogeneous). This means that you may need to invent some new syntax in order to express the type of compose as you have it. Here's how Ramda (a functional utility library for JS) describes the type of R.compose:
((y → z), (x → y), …, (o → p), ((a, b, …, n) → o)) → ((a, b, …, n) → z)
It uses this … syntax in order to represent a variadic function. The right-most function in the parameter list is of type ((a, b, …, n) → o), meaning that it is a variadic function that returns an o. This o is then used as the input of the next function which is of type (o → p). This continues down the list of parameters until the left-most function is of type (y → z), where z becomes the resultant type of calling the returned function: ((a, b, …, n) → z).
The type of binary function composition is much easier to express in Hindley–Milner notation:
// compose :: (b -> c) -> (a -> b) -> a -> c
const compose = f => g => x => f (g (x));
I'm trying to figure out if there is a pattern for writing pointfree composed function when the arguments should be spreaded in curried composing functions
i.e (with Ramda):
add_1_and_multiply = (add, mul) => R.compose(R.multiply(mul), R.add(1))(add)
add_1_and_multiply(3, 5) // 20
How to write add_1_and_multiply in pointfree style?
I'm not sure if you can easily combine pointfree style and non-unary arity.
Think first what should be the type of the resulting and composed functions:
// Compose: (B -> C) -> (A -> B) -> A -> C
const compose = f => g => x => f(g(x))
// Add: A -> A -> A
const add = x => y => x + y
// Mul: A -> A -> A
const mul = x => y => x * y
// Add1: A -> A
const add1 = add(1)
// Add1AndMul: A -> A -> A
// because:
// Add1: A -> A
// Mul: A -> A -> A
const add_1_and_mul = compose(mul)(add1)
// Mul4: A -> A
const mul_4 = add_1_and_mul(3)
const result = mul_4(5) //> 20
Ramda has uncurryN so you can wrap it around the compose and get rid of the currying of the resulting function.
const add_1_and_multiply = R.uncurryN(2, R.compose(R.multiply, R.add(1)))
let result2 = add_1_and_multiply(3, 5) //> 20
To add another function to the "chain" you need to compose it with previous function.
// Add1AndMul: A -> A -> A
const add1_mul = compose(mul)(add1)
This is our desired signature.
// 1 2 3
// Add1AndMulAndAdd: A -> A -> A -> A
// which is: | | |
// Add1: A -> A | |
// Mul: A -> A -> A |
// Add: A -> A -> A
So somehow we have to pass those A2 and A3 without any "points".
Let's try just simple composition and analyze it:
let add1_mul_add = compose(add)(add1_mul)
Remeber signature of compose: (E -> F) -> (D -> E) -> D -> F!
Analyzing it in steps:
We supply our add function signature instead of (E -> F)
(E -> F )
(A -> A -> A)
We conclude that
E = A
F = A -> A
We do the same to (D -> E) and add1_mul
(D -> E )
(A -> A -> A)
We conclude that
D = A
E = A -> A
But we can already see a contradiction there!
Conclusion in step 2 contradicts conclusion in step 1:
E cannot be A and A -> A at the same time.
Therefore we cannot compose add and add1_mul and our add1_mul_add will throw an error.
Let's try to workaround the problem and fix it breaking our promise of pointfree style.
const add1_mul_add = x => compose(add)(add1_mul(x))
I'm going to break some rules and mix signatures with code to illustrate my point:
x -> (A -> A -> A) -> (x -> A -> A) -> A -> A -> A
||
\/
x -> (A -> A -> A) -> (A -> A) -> A -> A -> A
(E -> F ) -> (D -> E) -> D -> F
So we got our correct compose signature! How to get rid of the x variable to go back to pointfree?
We can try to look for obvious patterns, like for example... our ye olde function composition!
f(g(x)) => compose(f)(g)
And we find this pattern in our new add1_mul_add -
f = compose(add)
g = add1_mul
f(g(x)) = compose(add)(add1_mul(x))
And we reduce it to pointfree and we got our new add1_mul_add function:
const add1_mul_add = compose(compose(add))(add1_mul)
But hey - we can reduce it even more!
const add1_mul_add = compose(compose)(compose)(add)(add1_mul)
And there we have found something that already exists in haskell under the name of The Owl.
We can define it in Javascript simply as:
const owl = compose(compose)(compose)
But now, with every new function in the chain, you would have to create a higher order of the owl operator.
const owl2 = compose(compose)(owl)
const add1_mul_add_mul = owl2(mul)(add1_mul_add)
const owl3 = compose(compose)(owl2)
const add1_mul_add_mul_add = owl3(add)(add1_mul_add_mul)
So I really recommend having your functions unary in pointfree style. Or use other constructs like lists:
const actions = [ add, mul, add, mul ]
const values = [ 1, 2, 3, 4 ]
const add_mul_add_mul = (...values) => zip(actions, values).reduce((acc, [action, value]) => action(acc, value), 0)
I have a situation where I need to do this:
const f = (obj) => assoc('list', createList(obj), obj)
Due to the fact that I need the argument for the second and the third parameter, prohibits me from doing something like:
const f = assoc('list', somehowGetObj())
I also tried this, but that didn't work:
const f = assoc('list', createList(__))
const f = converge(assoc, [createList, identity])
Is there a proper way to do this by currying?
Another option is
chain(createList, assoc('list'))
which you can see in action on the Ramda REPL.
Update
For further explanation of how this works, I'll use the variation which will work with the next release of Ramda:
chain(assoc('list'), createList)
to show how it matches the current signature:
chain :: Chain m => (a -> m b) -> m a -> m b
Ramda treats functions as FantasyLand Monads, and therefore thus also as Chains. So to specialize the above to functions, we have
chain :: (a -> Function x b) -> Function x a -> Function x -> b
but Function x y can be written more simply as x -> y, so the above can written more simply as
chain :: (a -> x -> b) -> (x -> a) -> (x -> b)
Then you can use these (specialized) types:
createList :: OriginalData -> YourList (x -> a)
assoc :: String -> YourList -> OriginalData -> EnhancedData
assoc('list') :: YourList -> OriginalData -> EnhancedData (a -> x -> b)
and hence
chain(assoc('list'), createList) :: OriginalData -> EnhancedData (x -> b)
const f = converge(assoc('list'), [createList, identity])
Javascript has a poorly constructed but convenient "arguments" variable inside every function, such that you can pass arguments through a function like so:
function foo(a, b, c) {
return bar.apply(this, arguments);
}
function bar(a, b, c) {
return [a, b, c];
}
foo(2, 3, 5); // returns [2, 3, 5]
Is there an easy way to do a similar thing in Python?
>>> def foo(*args):
... return args
>>> foo(1,2,3)
(1,2,3)
is that what you want?
Yeah, this is what I should have said.
def foo(*args):
return bar(*args)
You don't need to declare the function with (a,b,c). bar(...) will get whatever foo(...) gets.
My other crummier answer is below:
I was so close to answering "No, it can't easily be done" but with a few extra lines, I think it can.
#cbrauchli great idea using locals(), but since locals() also returns local variables, if we do
def foo(a,b,c):
n = "foobar" # any code that declares local variables will affect locals()
return bar(**locals())
we'll be passing an unwanted 4th argument, n, to bar(a,b,c) and we'll get an error. To solve this, you'd want to do something like arguments = locals() in the very first line i.e.
def foo(a, b, c):
myargs = locals() # at this point, locals only has a,b,c
total = a + b + c # we can do what we like until the end
return bar(**myargs) # turn the dictionary of a,b,c into a keyword list using **
How about using * for argument expansion?
>>> def foo(*args):
... return bar(*(args[:3]))
>>> def bar(a, b, c):
... return [a, b, c]
>>> foo(1, 2, 3, 4)
[1, 2, 3]
I think this most closely resembles your javascript snippet. It doesn't require you to change the function definition.
>>> def foo(a, b, c):
... return bar(**locals())
...
>>> def bar(a, b, c):
... return [a, b, c]
...
>>> foo(2,3,5)
[2, 3, 5]
Note that locals() gets all of the local variables, so you should use it at the beginning of the method and make a copy of the dictionary it produces if you declare other variables. Or you can use the inspect module as explained in this SO post.