One of the applications of Applicative in Haskell is to be able to take a function with multiple arguments and feed Functors to it in a readable way
Just (\x y -> x + y) <*> Just 3 <*> Just 4
(\x y -> x + y) <$> Just 3 <*> Just 4
Now in Javascript, we cannot create infix operators, so the next best thing is chaining, right? So what is lacking in the spec (If I'm not mistaken) is something of the form
apply :: Apply f => f (a -> b) ~> f a -> f b
So that you can do (in Javascript)
Just(x => y => x + y) .apply(Just(3)) .apply(Just(4))
Which reads a lot like
(x => y => x + y) (3) (4)
I just called it apply here, but that may not be the best name. In any case, I just started diving into the functional rabbit hole a week ago, so I might be entirely mistaken here.
Just (\x y -> x + y) <*> Just 3 <*> Just 4 (\x y -> x + y) <$> Just 3 <*> Just 4
Here are the direct equivalents of the above, @rikkertkoppes, using ap and map:
> S.ap(S.ap(S.Just(x => y => x + y), S.Just(3)), S.Just(4))
Just(7)
> S.ap(S.map(x => y => x + y, S.Just(3)), S.Just(4))
Just(7)
The second expression can be written in terms of lift2:
> S.lift2(x => y => x + y, S.Just(3), S.Just(4))
Just(7)
The problem I have with your first two examples is the excessive nesting, which gets even worse for 3 arguments. There is no way out of this, as far as I can see, which is (as I understand) exactly the reason the haskell infix operator <*> exists.
Using lift2 reads a lot better indeed, but is far less generic. Specifically, in Sanctuary there is no lift4 (one may argue about whether its wise to have a function with that many arguments though)
I can see there are equivalents, but I don't believe they are as readable as method chaining my apply method.
Also, when comparing the approach with chain
Just(4) >>= (\x -> Just(x * 2)) >>= (\x -> Just(x + 1))
Translates very simply to [1]
Just(4) .chain (x => Just(x * 2)) .chain (x => Just(x + 1))
So here .chain is the direct equivalent of >>=. In the former .apply is the direct equivalent of <*>
[1] ok, I made a conscious effort to make the syntaxes similar
The nesting is certainly unfortunate. Method chaining, as you've suggested, is likely the best way to approximate <*>. apply is not part of the FL spec, but perhaps a library such as Folktale could provide this (or already does so).
Thanks for the pointer. Folktale indeed implements it the way I proposed, but calls it ap
var Just = require('data.maybe').Just;
Just(x => y => x + y) .ap (Just(3)) .ap (Just(4));
Which is fully equivalent to the following Haskell
Just(\x y -> x + y) <*> (Just(3)) <*> (Just(4))
I like the symmetry here.
Thanks for the responses. Learned a lot
I just bumped into this with parsimmon, which complies with the spec. Can someone explain to me why this reverse ordering of application was picked? Is there any benefit? It seems to completely destroy the beauty of the applicative application ordering, especially with something like an applicative parser. I have to write
P.of(6).ap(P.of(5).ap(P.of(R.add))).parse('')
which is essentially a postfix, nested version of
P.of(R.add).ap(P.of(5)).ap(P.of(6)).parse('')
Incidentally, R.ap, when used with a list, does not use this particular ordering, so does that mean it's not spec compliant?
R.ap([R.identity, R.add(1)], [2])
// [ 2, 3 ]
And sanctuary implements the same ordering https://sanctuary.js.org/#ap. So is this also not FL Apply compliant?
@theqabalist
Can someone explain to me why this reverse ordering of application was picked?
Reasons for the change are given in #50.
Incidentally, R.ap, when used with a list, does not use this particular ordering
It uses an ordering consistent with map and chain.
In Jabz I have solved this problem by making a lift function part of the applicative interface.
Not only is it a lot more convenient to use. It also performs a lot better than manually using ap and map. In my blog post about Jabz I show a benchmark in which lift on a Maybe is 5 times as fast as using ap. For other applicatives the performance benefits can be even larger.
If there is any interest in this I can do a PR that adds lift to the applicative spec.
Incidentally, R.ap, when used with a list, does not use this particular ordering, so does that mean it's not spec compliant? -- @theqabalist
R.ap is a static function, Fantasy Land does not specify how similarly named static functions should behave - they can do whatever they want, and they choose the argument ordering most convenient in practice. That said, Ramda's ap dispatches to applicative.ap() in the wrong way (they still expect applicative interface to follow Fantasy Land 0.x). It is in this way that Ramda is indeed not spec compliant.
And sanctuary implements the same ordering https://sanctuary.js.org/#ap. So is this also not FL Apply compliant?
S.ap is also a static function. However, it does dispatch to the correct Fantasy Land 3.x interface. It's very similar to other dispatches such as S.map and R.chain, in that they all flip the arguments before dispatching. This fact also highlights how fantasy-land/ap is more aligned with all the other methods under the new specfication.
I recently realized that the ap()-method, like the ap()-function, is not specified by fantasy land. Technically the only thing Fantasy Land says anything about is the fantasy-land/ap()-method.
That lead me to implement ap (which is intended for users, as opposed to fantasy-land/ap) in the pleasant "fluent" way: https://github.com/fluture-js/Fluture/issues/87 https://github.com/fluture-js/Fluture/pull/96. This comes from the realization that the un-prefixed ap method only exists to provide an interface for the end-user.
Incidentally, this change also re-introduced compliance to Fantasy Land 0.x, allowing outdated libraries like Ramda to dispatch to Fluture#ap.
@paldepind
If there is any interest in this I can do a PR that adds lift to the applicative spec.
I think it would be added to the _Apply_ spec.
I'm _definitely_ interested in adding utilities to the spec. See https://github.com/sanctuary-js/sanctuary/issues/392. There might be pushback on adding a variadic lift though. Perhaps lift2?
It could also be an alternate minimal complete definition of Apply.
@Avaq, @theqabalist with today's release of https://github.com/ramda/ramda/tree/v0.24.0, Ramda is now FL v1 compliant 🎉
@gabejohnson
I think it would be added to the Apply spec.
Yes, you are right.
I'm definitely interested in adding utilities to the spec. See sanctuary-js/sanctuary#392. There might be pushback on adding a variadiclift though. Perhaps lift2?
ap is basically lift1. Adding lift2 is not good enough. It makes the problem one step smaller but doesn't eliminate it.
I know some people don't like variadic functions. But lift is one of the cases where a variadic function is truly the right thing IMO. Actually, a variadic lift looks a lot like idiom brackets from the original applicative paper which is a very convenient way to work with applicatives. Other languages bake in special syntax to get such convenience. I think it's wonderful that we in JS can just implement it with a variadic function.
In most cases having to write functions like lift2, lift3, etc. is an unfortunate thing that one has to put up with in languages that don't support variadic functions. When such fixed arity functions are desired for the purpose of currying they can easily be implemented on top of the variadic lift because the variadic lift is just a generalized liftX.
Forgive me for being dense, I read through #50, and it still makes no sense to me. Ap was not inconsistent with map or chain because the _value_ contained in the functor is a function. This is out of sync with both haskell and purescript in terms of how the functions are defined. This definition of ap is a combination of applicative apply and the thrush combinator it seems to me. Lift has a problem in that it requires a spatially/temporally bound application of the function. If I want to ap here, then ap later, then finally ap and get a value yet later, I cannot do that with lift. It's honestly just bizarre to me that FL parted with a long standing convention that comes from Haskell and is replicated in purescript.
This just confused me also, although I don't know what the best or _most correct_ solution would be.
Specifically, both folktale @robotlolita (i.e. from v1 data.task) and the FP tutorials by Brian Lonsdorf @DrBoolean (i.e. either.ap and example usage) take the approach where values are applied to a function, rather than the spec here which applies a function to a value.
Aren't these 2 different things?
Function applied to a value
Value applied to a function
This certainly confused me for a while as most of the resources I've been following use the latter, whilst FL spec uses the former. I'm not suggesting which is correct, just that it is pretty useful to have a specification for both use cases rather than just one of them.
Aren't these 2 different things?
Function applied to a value
Value applied to a function
We evaluate f(x) by applying f to x. This is function application.
Describing this as _applying x to f_ is incorrect, but commonly heard in informal settings.
I don't think your comment applies only to fantasy-land/ap, @mattstyles. Several methods could benefit (in some contexts) from having their “operands” flipped. We may wish a.concat(b).concat(c) to mean a <> b <> c in one context but c <> b <> a in another.
I'm not suggesting which is correct, just that it is pretty useful to have a specification for both use cases rather than just one of them.
Library authors are free to provide two versions of ap, but I don't think Fantasy Land should require both versions. Simplicity should trump convenience.
Library authors are free to provide two versions of
ap
And many do - in fact, Fluture provides three:
Fluture.ap :: Apply m => m (a -> b) -> m a -> m b - An ap function with an argument order aimed at partial function application and composition.Fluture.prototype.ap :: Future e (a -> b) ~> Future e a -> Future e b - An ap method with an argument order aimed at fluent method chaining.Fluture.prototype['fantasy-land/ap'] :: Future e a ~> Future e (a -> b) -> Future e b - The ap method as specified by Fantasy Land, for the purpose of interoperability. We don't care about the argument order with this one: it's just whatever it needs to be, to be compliant. It doesn't get in anybodies way, because it's namespaced.Describing this as applying x to f is incorrect, but commonly heard in informal settings.
Just(f).ap(Just(1)).ap(Just(2)) or f.apply(null, [1, 2]: f is applied to 1, 2
Just(2).ap(Just(1).ap(f)): Don't know how to translate this to English in same the syntatic order...
The spec will not change given that the current order is consistent with that of fantasy-land/map and fantasy-land/chain. As @Avaq and others have mentioned, ADT authors are free to provide ap as a flipped fantasy-land/ap for users who prefer method chaining to function composition.
Most helpful comment
Forgive me for being dense, I read through #50, and it still makes no sense to me. Ap was not inconsistent with map or chain because the _value_ contained in the functor is a function. This is out of sync with both haskell and purescript in terms of how the functions are defined. This definition of ap is a combination of applicative apply and the thrush combinator it seems to me. Lift has a problem in that it requires a spatially/temporally bound application of the function. If I want to ap here, then ap later, then finally ap and get a value yet later, I cannot do that with lift. It's honestly just bizarre to me that FL parted with a long standing convention that comes from Haskell and is replicated in purescript.