Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the Fantasy Land Spec? #352

Closed
mkaemmerer opened this issue Apr 1, 2014 · 10 comments
Closed

Implement the Fantasy Land Spec? #352

mkaemmerer opened this issue Apr 1, 2014 · 10 comments

Comments

@mkaemmerer
Copy link

Bacon could easily be made to implement the Fantasy Land Specification (https://github.com/fantasyland/fantasy-land), which defines a number of interfaces for algebraic structures.

Here's what it would take:

Add observable.chain as a synonym for observable.flatMap
Add Bacon.empty as a synonym for Bacon.never
Add Bacon.of as a synonym for Bacon.once
Add observable.ap

Observable.ap = x -> this.combine(x, (f,x) -> f x)

To be honest, I don't know how much implementing this spec would help Bacon. On the other hand, Bacon could be instrumental in spreading adoption of Fantasy Land. Thoughts?

@raimohanska
Copy link
Contributor

There's an old branch fantasy-land where I tried this already.

The problem is that they (er, @puffnfresh) say that the chain and ap methods should be consistent. That is theoretically nice, but then ap would have to be based on flatMap instead of combine. And that doesn't make sense for Observables with more than 1 value, because the result of ap would be a sort of a cartesian product of the streams.

So the options would be

  1. make ap based on flatMap, get useless ap impl
  2. remove Monad, make ap based on combine
  3. screw the rules, have chain based on flatMap and ap based on combine

Whaddyathinks?

@mkaemmerer
Copy link
Author

  1. Define chain as flatMapLatest. I think this gives us a sane definition for ap.

I'm okay with either 1 or 4 (assuming the monad laws do in fact work out with 4 the way I think they do). Having a Monad instance seems much more useful than having an Apply instance, so I'd rather avoid option 2.

@raimohanska
Copy link
Contributor

I think the only sane definition of ap would be based on combine. The flatMapLatest based one is a messed-up version of zip that no-one would actually use. Sorry :)

@raimohanska
Copy link
Contributor

I mean would you ever combine two existing streams using flatMapLatest? Applicative is about combining and the ways you actually combine streams in Bacon are

a) combine
b) zip
c) sample (this is a bit too asymmetric to consider for ap though)

@mkaemmerer
Copy link
Author

I'm not quite sure I understand.

To clarify, here are two definitions of ap:

observable.ap1 = x => @combine(x, (f,x) -> f x)
observable.ap2 = x => @flatMapLatest( f -> x.map(f) )

Would you agree that ap1 is a reasonable definition? If so, is there a counterexample you can think of where f.ap1(x) !== f.ap2(x)? I believe these two definitions are in fact equivalent.

@raimohanska
Copy link
Contributor

No they're not :)

@mkaemmerer
Copy link
Author

Ah... I see the problem now. Combine produces a value as soon as the observable function changes, whereas flatMapLatest doesn't produce a new value until the observable value changes.

@raimohanska
Copy link
Contributor

That's right. You can try it visually here: http://bacon-game.herokuapp.com/#4

@phadej
Copy link
Member

phadej commented Apr 8, 2014

I'd like to point out that Properties and EventStreams behave so differently, that single definition for Applicative or Monad -instances will not work for both.

Properties have a concept of current value. For them of could just wrap a constant value as property, and ap will be definable in terms of combine. However defining chain is problematic, for many reasons. One practical one, is that flatMap* functions return EventStreams :) but seriously, IMHO you don't even need chain for Properties.

EventStreams are different beasts. If you think what should the ap and of to be, so that laws are satisfied, not so easy. One solution is ap defined in terms of zipWith, and of defined as a stream producing a value on each scheduler tick. (Resembling Haskell's ZipList).

The chain for that definition would be flatMapLatest.

And merge for SemiGroup's / Monoid's concat.

@semmel
Copy link
Member

semmel commented Jul 2, 2022

The problem is that they (er, @puffnfresh) say that the chain and ap methods should be consistent.

Probably the condition for deriving ap from chain.

ap(mf, ma) ≡ chain(f => map(f, ma), mf)

What still "sort-of" works for single temporal values from asynchronous computations, breaks down for temporal streams of values: chain means to merge a higher order stream, and

I think the only sane definition of ap would be based on combine.

ap and chain do no longer fit together for reactive streams.

How bad would it be to "screw that rule" and implement ap ≅ combine(f => a => f(a)) and chain ≡ flatMap ? On could still derive useful functions like liftA2 from FL.ap and FL.map etc.

It is certainly useful to have FL "light" compatibility, but is it also too dangerous?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants