Hi guys,
I've been implementing FantasyLand in monet.js and I've come a little unstuck with ap().
Unless I've got the wrong end of the stick FantasyLand's ap() demands that the applicative already contains a function and then accepts an applicative with a value.
appWithFunction.ap(appWithValue)
Unfortunately I've followed scalaz & functional java approach which does the opposite.
appWithValue.ap(appWithFunction)
See here for the Maybe example: http://cwmyers.github.io/monet.js/#apmaybefn
Here is an example from FunctionalJava Option:
public final <B> Option<B> apply(Option<F<A,B>> of)
So my question is why has Fantasy Land taken this particular approach to applicative? I'm worried that this would be a large breaking change to the monet library and I'm trying to figure out what the right thing is to do.
Cheers,
Chris
I guess I copied Haskell's <*> infix operator. Just like <$> (i.e. infix fmap) - it's flipped to what it probably should be.
It'd probably be more consistent to flip it (i.e. it would look more like other functors).
What should we do about updating the specification? I have no hesitation to release new versions of my projects but does anyone have a project where they can't or don't want to change the order of ap?
I think this goes back to #24. However, we haven't really been keeping up with updating the version number to begin with. If you'd like I can go back to when version 0.0.1 was first "released" and look through the changes since then, tagging and whatever. Or we can just start right now with a set version, and keep everything semver'd from here on.
There's an alternative definition of Applicatives that uses a "join" or "seq" operator instead of "ap".
Here's how join (unrelated to join :: m (m a) -> m a) would look (note: code in this comment is pseudo-haskell):
class Functor f => Joinable f where
pure :: a -> f a
join :: f a -> f b -> f (a, b)
It's equivalent to ap:
join u v = ap (map (,) u) v
ap u v = map (\(f, x) -> f x) (join u v)
And it even comes with equivalent laws:
join (pure x) v = map (x,) v
join v (pure x) = map (,y) v
join (map f u) (map g v) = map (f *** g) (join u v)
join u (join v w) = map (\(x, (y, z)) -> ((x, y), z)) (join (join u v) w)
map f (pure x) = point (f x)
The "ap"-based one seems to be the best in Haskell since functions are auto-curried and tuples are non-recursive. But in JS we can use heterogenous lists instead, so "join" might be more natural to express. In fantasyland, I guess it would be something like "a.join(functors)" is a functor of a combined array of results from the given functors.
Anyways, slightly rambly, but this represents the way the seq method is used in Parsimmon, which is much more naturally usable than ap (in fact, Parsimmon's ap is implemented in terms of seq). It'd be worth considering whether to standardize this sort of interface, since I can imagine it coming up quite a lot.
related to #56, although liftN still requires a function.
appWithValue.ap(appWithFunction)
looks really odd to me. Applying functions with multiple arguments doesn't seem to work well - or is quite ugly, if we look the linked example:
var person = function(forename, surname, address){鈥.curry() var maybePersonResult = maybeAddress .ap(maybeSurname .ap(maybeForename .map(person)))
Not only is there much nesting, but also the order of the arguments is inverted. Having used .map() instead of .ap(Maybe.of()) might save characters and an explicit Maybe reference, but doesn't make it look better.
The appWithFunction.ap(appWithValue) order is better imho:
Maybe.of(person).ap(maybeForename).ap(maybeSurename).ap(maybeAddress)
// or even
Maybe.of(person).ap(maybeForename, maybeSurename, maybeAddress)
It wouldn't be more consistent to flip it just because we have flipped fmap to become a .map method of a Functor instance - Applicatives are _more_ than Functors and .ap can already be a method of an Applicative instance.
In Parsimmon I've recently gone with the join implementation I mentioned here, although I renamed it to seq to match Parsnip. Basically, seq :: [Parser *a] -> Parser [*a] where *a is heterogenous. ap is implemented in terms of this primitive.
Yeah, this needs to be fixed. PR would be awesome!
Should we flip the order of ap, or just live with it?
I'm having trouble typing ap() with Flow, ended up with this:
class Stream<T> {
// Apply
ap<A,B>( other:Stream<A> ): Stream<B> {
// In order for .ap() to work `this` must be a `Stream<A => B>`,
// but I don't know how to express that constraint in Flow, so just use `any`
const streamF:BasicStream<any> = this.observe
return new Stream(bs.ap(streamF)(other.observe))
}
}
Would be straightforward if it was other way around.
That is quite unfortunate. Can you actually write the other way with flow?
Sure, with reversed version of ap it will look like this:
class Stream<T> {
ap<A>( other:Stream<( x:T ) => A> ): Stream<A> {
return new Stream(bs.ap(other.observe)(this.observe))
}
}
For the record: not a big problem for me, just wanted to add my two cents :)
I've been hit with this recently as well. So the question is how do we change it, because I think we should!
Could we maybe rename ap to something else, or do we say if you depend on the new fantasy-land #92 ap works like this now?
We could certainly use NPM to help us manage changes like that after libraries start depending on FL.
We could do for example:
1.0 backward compatibility release with unprefixed names and no changes to the spec2.0 adds prefixes therefore providing clashes safety, but still no changes to the spec3.0 no changes in method names, but includes changes to spec like this and #88Then libraries could specify appropriate versions range in their peerDependency to fantasy-land, and consumers won't be able to use two incompatible libraries (they won't be able to install version of fantasy-land that satisfies peerDependency for all of incompatible libs).
I'd rather have 3.0 out before 2.0 :dancer:
No preference on that matter from me :)
We could also squash 3.0 and 2.0 to one version, btw.
So you only want to change this because flow's type system is not sophisticatd enough?
Yep, and also the fact that it's inconsistent with map and chain and was a mistake from the beginning (see https://github.com/fantasyland/fantasy-land/issues/50#issuecomment-27708812)
Feel free to discuss in the PR: #145
I think this can be closed now, no?
Indeed it can!
Sorry for commenting on closed issue, but It may help somebody dealing with the same issue as I did. I heavily use monet.js algebraic structures along with ramda. As @cwmyers pointed out, applicatives in monet.js are not compatible with fantasy-land spec. Though one cannot use ramda's liftN to work with monet's applicates. I implemented liftFN to create interop for monet.js and ramda. It is part of ramda-adjunct and you can find more information in the docs.
@char0n looks like ramda#1900 was merged. So soon, very soon, once ramda releases its next version we should all be gtg!!
@evilsoft I've just tried using lift from master branch of ramda, but it still doesn't work with monet.js algebraic structures. liftFN from ramda-adjunct still working. I am still investigating the issue...
I have one additional question regarding composition of Apply spec regarding v.ap(u.ap(a.map(f => g => x => f(g(x))))) is equivalent to v.ap(u).ap(a) (composition)
const add3 = curry((a, b, c) => a + b + c);
const m10 = Maybe.Some(10);
const m15 = Maybe.Some(15);
const m17 = Maybe.Some(17);
m10.ap(m15.ap(m17.map(add3)))
What is the eqvivalent of v.ap(u).ap(a) in my real code example ? Is this type of composition still possible when the ap argument must be apply of a function ?
Update:
monet.js PR #112 makes monet.js compatible with fantasy-land spec and next version of ramda (>0.23.0). Use [ramda-adjunct](https://github.com/char0n/ramda-adjunct} and liftFN for time being.
Most helpful comment
looks really odd to me. Applying functions with multiple arguments doesn't seem to work well - or is quite ugly, if we look the linked example:
Not only is there much nesting, but also the order of the arguments is inverted. Having used
.map()instead of.ap(Maybe.of())might save characters and an explicitMaybereference, but doesn't make it look better.The
appWithFunction.ap(appWithValue)order is better imho:It wouldn't be more consistent to flip it just because we have flipped
fmapto become a.mapmethod of a Functor instance - Applicatives are _more_ than Functors and.apcan already be a method of an Applicative instance.