-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Sketch of approach to fix #168 #370
Conversation
trait ApplicativeFromMonad[F[_]] extends Applicative[F] with FunctorFromApplicative[F] { self: Monad[F] => | ||
def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] = | ||
flatMap(ff)(f => flatMap(fa)(a => pure(f(a)))) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, one question I have is -- why are we structuring these traits using self-types? My natural inclination would have been to define something like:
trait ApplicativeFromMonad[F[_]] extends Monad[F]
with FunctorFromApplicative[F] {
def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] =
flatMap(ff)(f => flatMap(fa)(a => pure(f(a))))
}
Is there a reason this doesn't work? The only reason I ask is that seen in isolation its purpose might not be totally apparent. In fact, defined this way, we could name them CanonicalMonad[F]
, CanonicalApplicative[F]
, and so on.
What d you think? Is there something I'm missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This works. If the purpose is not apparent I will go with your approach, the only reason to go with self-types here was because I thought they expressed the intent better.
I read the signature
trait FunctorFromApplicative[F[_]] extends Functor[F] { self: Applicative[F] =>
as "This trait provides a Functor[F] when mixed into an Applicative[F]".
Conversely, I don't believe the signature
trait CanonicalApplicative[F[_]] extends Applicative[F] {
provides as much information about how it is intended to be used (as a mix-in).
I expected the mix-in approach to scale better when you want to pick-and-choose which defaults you want -- for example
implicit val optionInstance =
new Monad[Option] with Traverse[Option] with FunctorFromTraverse[Option] {
...
}
and
implicit val optionInstance =
new Monad[Option] with Traverse[Option] with FunctorFromApplicative[Option] {
...
}
should have different implementations of map.
Current coverage is
|
Hi @woparry, sorry for the radio silence. I've been busy and I'm trying to consider exactly how we should structure something like this (and how we should talk about it). |
A `monadFilterConsistency` law was added in 8f7a110 but wasn't hooked up in the tests. This might be better accomplished with something like typelevel#370, but in the interim I think we should at least hook it up.
Hello, did you also consider
I could help you with this see http://homepages.inf.ed.ac.uk/wadler/papers/arrows-and-idioms/arrows-and-idioms.pdf |
This PR looks pretty stale, can we close it out? |
Closing stale PRs. Feel free to reopen if there is interest to revive the effort. |
This is the first set of tests that verify your typeclass implementations are interchangeable with the defaults.
The pieces:
FunctorFromApplicative and ApplicativeFromMonad are mixins that define (map, ap) in terms of (ap, flatMap). They make it easy to build instances of Monad and Applicative by only defining the core functions (pure and (ap or flatMap)).
Applicative, FlatMap, Traverse, etc no longer provide default implementations of ap and map.
You must override them yourself with an efficient version (recommended) or mix in the appropriate trait from canon to get the defaults.
The canon package object get canonicalMonad and canonicalApplicative, which use the mixins to build an implementation using the default versions of derived functions.
The Laws use these to verify that the instance implementations of (map, ap, map2) match the canonical implementations.