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

Decouple Monad from Applicative #42

Closed
raimohanska opened this issue May 28, 2013 · 9 comments
Closed

Decouple Monad from Applicative #42

raimohanska opened this issue May 28, 2013 · 9 comments

Comments

@raimohanska
Copy link
Contributor

I'm afraid it's not always correct to assume that a Monad is also an Applicative. At least I tried to implement the specs into Bacon.js today, for an excersise, and run into issues because of this assumption. I'm not sure how familiar you're with Bacon.js, but I'll try to explain the case anyway.

Now an EventStream in Bacon.js is clearly a Monadic structure: the flatMap method (aliased now as chain) of EventStream has one parameter which is a function that creates a new EventStream for each value in the original stream. In combination with EventStream.of we have a nice Monad. Except.

The Applicative interface for EventStreams doesn't make much sense, at least if based on flatMap: the result stream a.ap(b) would take apply each function from the source stream to all (or one?) subsequent elements in the stream b. It would make more sense to base ap on combine or even zip, but I'd rather not have EventStream implement Applicative because of this ambiguity.

In Haskell, you can declare Monad without Applicative. Why not in Fantasy Land?

The fantasy-land version of Bacon.js is in the fantasy-land branch. I'm planning to bring that to master, if I can come up with a nice solution. The current EventStream/Applicative one seems to be compliant, but I would never use it because Applicative through flatMap just makes no sense.

I'd like to have EventStream implement Functor and Monad (no Applicative), and Property implement Functor and Applicative (and a Duck Monad (Property.flatMap returns an instance of EventStream)).

Please ask for more details if this made no sense to you :)

@raimohanska
Copy link
Contributor Author

I added Monoid for EventStream. It already had concat so I only had to add a synonym EventStream.empty for Bacon.never. What a fun excercise!

@Twisol
Copy link

Twisol commented May 28, 2013

In Haskell, you can declare Monad without Applicative. Why not in Fantasy Land?

This is a known issue, and Haskellers are generally unsatisfied with the current state of affairs. The Haskell situation is an artifact of monads being used in Haskell early on (to control side-effects and I/O), before applicatives were also found to be generally useful. The Monad and Applicative class instances are expected to agree even though the standard library doesn't require it. Mathematically, all monads are applicative functors by definition.

I don't know Bacon.js well enough to suggest any specific alternatives. Does your flatMap meet the Monad axioms in all cases (barring unavoidable semantics of mutation)? What if you implemented flatMap in terms of combine/zip - what effects would that have on the monadic semantics?

@markandrus
Copy link

chain and map give you ap. In the Fantasy Land spec:

  • Applicative's ap; derivable as function(m) { return this.chain(function(f) { return m.map(f); }); }

Did you try this default definition? There may be multiple and sometimes more useful ways to write ap. EventStream might be one such case, but I don't know Bacon.js.

@raimohanska
Copy link
Contributor Author

Guys, thanks for your replies!

@markandrus My implementation of EventStream.ap is identical to the default one. If I have to choose one, I might go for the zip based approach, though. That might be useful for someone :)
@Twisol I'm tempted to say it meets the Monad axioms. Are we talking about the laws in here?
@Twisol you cannot implement flatMap in terms of combine/zip, can you?

@raimohanska
Copy link
Contributor Author

The issue is not whether I can implement Applicative, but more that I'd rather not because of the many different possible ways.

Like, if [] is Applicative, should it use >>= or zip? Depends on situation, probably.

@Twisol
Copy link

Twisol commented May 28, 2013

@raimohanska RIght, those laws. It's best if you semi-rigorously prove that those axioms hold for your implementation, since it's easy to miss important edge cases.

When I said "implement it in terms of combine/zip", I meant that there may be a Monad implementation associated with the combine/zip-based Applicative you prefer, so you may want to start there and find a new flatMap for which the automatically-provided ap is equivalent to the new one. See if the semantics for the new flatMap make sense.

It may turn out that either of your ap and flatMap definitions don't preserve the appropriate axioms, or that you really do have two interesting sets of instances. Integers have at least two Monoid instances - Sum and Product - so this isn't obviously impossible. I would be a little surprised though.

@puffnfresh
Copy link
Member

Yes, Haskell stuffed up by not realising the hierarchy of Apply => Applicative => Monad. Fantasy Land doesn't have that problem.

Anyway, even in Haskell, for every Monad you can get an Applicative - the opposite is of course not true.

instance Monad m => Applicative (WrappedMonad m) where
    pure = WrapMonad . return
    WrapMonad f <*> WrapMonad v = WrapMonad (f `ap` v)

Where Haskell's ap is actually defined over Monad:

ap :: (Monad m) => m (a -> b) -> m a -> m b
ap = liftM2 id

liftM2  :: (Monad m) => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 f m1 m2 = do
  x1 <- m1
  x2 <- m2
  return (f x1 x2)

So if we can implement chain and of for EventStream but the above doesn't work, then the Monad laws must be broken somewhere 😦

And yes, things can have multiple Applicatives. That's what I was trying to get at with this part of the spec:

If there's more than a single way to implement the methods and
laws, the implementation should choose one and provide wrappers for
other uses.

Haskell does the same thing:

newtype ZipList a = ZipList { getZipList :: [a] }

instance Applicative ZipList where
    pure x = ZipList (repeat x)
    ZipList fs <*> ZipList xs = ZipList (zipWith id fs xs)

I'd say the Applicative for a data type should do the same as the Monad (if one is defined). That'd be the least surprising. Provide a wrapper for the other behaviour(s).

@philipnilsson
Copy link

An interesting feature of Applicatives is that they can not be used to change the 'control flow'. E.g. for IO

(\fire -> if fire then a else b) <$> fireZeMissiles <*> petTheKoala

will result in the side effects of fireZeMissiles always executing, even if fire is false. The applicative interface could perhaps (for IO) define a combinator that runs its arguments in parallell, as the order things complete does not affect the result.

Perhaps one could standardize on a "parallel applicative" type class, which the zip-applicative for lists and event streams could fulfill, as they are in a sense also "parallel".

@SimonRichardson
Copy link
Member

I'm closing this one, reopen if you wish to suggest other wise.

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

6 participants