purescript / purescript-prelude

The PureScript Prelude

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Having `ap` depend on `map` can introduce loops

hdgarrood opened this issue · comments

Before #229 was merged, ap depended on bind and pure. Now that #229 has been merged, ap depends on bind and map.

This means that defining apply = ap as well as map = liftA1 will cause loops. When you try to use map with such a type, the following happens:

  • map
  • is implemented via liftA1,
  • which calls apply,
  • which is implemented via ap,
  • ... which calls map, and we loop.

Effect is one such type; this was causing failures in purescript/purescript-refs#29 (comment).

I think the law changes in #229 were correct, but perhaps changing ap to have a Bind constraint rather than a Monad constraint should be reverted? I think changing the rules for when it's safe to use these default implementation functions is probably too dangerous a breaking change to be justifiable. Maybe default implementation functions should only be allowed to use type class members from classes which are below the class which they are providing a default implementation for in the hierarchy? That is, it's fine for liftA1 to depend on both apply and pure, because they are members from Apply and Applicative respectively, which are both below Functor. Also, it's fine for ap to depend on both bind and pure, because they are from Bind and Applicative, which are both below Apply. However, it's not fine for ap to depend on map, because that's from Functor, which is above Apply.

The change should probably be reverted until we can prevent this situation from occurring via a compiler error or something (and I'm not sure how difficult it would be to do that). I think it's too easy to make this mistake, even if it will rarely happen. It's not obvious what causes this loop. While we could document it, I don't think that's the solution that should be used here.

I agree that we shouldn’t leave ap as-is, but I would rather not just revert the whole change either. I think my preference right now would be to revert the code changes so that ap has a Monad constraint and depends on only bind and pure, but change the Bind class’s Apply Superclass law to this:

apply f x = f >>= \f’ -> map f’ x

so that we still ensure that Bind and Apply are compatible.