-
-
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
Separate default implementations from type class definitions #992
Comments
There is also the approach taken by scato, tracked in this issue : #963 which certainly looks interesting to me. I am for this, but whatever mechanism we end up choosing I'd also like the ability to still get the defaults, if only to get the ball rolling while experimenting. Maybe through a mix-in or something. |
One issue with the current approach is if you want to do MTL style programming, you end up having duplication. A short, contrived, example: trait MonadMagic[F[_]] extends Monad[F] {
def magic: F[Int]
} Now I want to define I suppose you could also do something like trait MonadMagic[F[_]] {
def monad: Monad[F]
} and provide an implicit conversion I'm hoping a solution to this ticket might also solve this problem. |
I have a few observations I'd like to make/points I would like to clarify.
and an associated
which reifies any methods on wdyt? |
This is a first-step for typelevel#992. I've started with the Bitraverse type hierarchy because it's not a very deep one. The idea is that we would continue this effort for other type class hierarchies.
at this point, do we still want to change type class encoding prior to 1.0.0 release? cc @ceedubs |
@kailuowang Strong 👎 from me on changing the encoding prior to 1.0. IMO, if we want to change the encoding, that should be a cats 2.0 thing at this point. |
Yeah I'm gonna have to agree. Otherwise cats-mtl would be in cats. |
According to consensus, moving it out of 1.0.0 |
There has been quite a bit of discussion about this going on for a long time, in #168, #370, on Gitter, and at the Typelevel Summit in Philly. But while #168 and #370 are highly relevant, there hasn't been an issue tracking this specifically.
At the Typelevel Summit in Philly, people seemed to come to a consensus that it would be good to not have type classes automatically provide default implementations of methods. For example, someone might want to define their own
Monad
instance without inheriting a default implementation ofmap
in terms offlatMap
andpure
(that is likely less efficient than the version ofmap
that they would otherwise define) .#368 is one approach to solving this problem. At the TL Summit, people seemed to be leaning toward a "minimum viable product" approach that is (arguably) a bit simpler. A type class
trait
(Monad
for example) would contain only abstractdef
signatures. Someone could extendMonad
without inheriting any default implementations. Each type classtrait
would have a correspondingtrait
that included default implementations (for examplemap
in terms offlatMap
andpure
on the trait corresponding to monad). So far all of the name suggestions that we have come up with for these traits with default implementations haven't seemed quite right.DerivedMonad
seems misleading, because it could be interpreted as aMonad
that is completely derived in a kittens-like fashion.MonadImpl
sounds a bit silly, but I guess at least it's concise.A major motivator for this is that currently it's really easy to not realize that you are inheriting a default implementation whose performance is really bad. I came to want this when I started toying with adding an
IsoFunctor
(basically a natural transformation in both directions) and I wanted to provide helpers that could create type class instances for your structure as long as you provided anIsoFunctor
between your structure and one that has the relevant type class instance. For example, if yourJsonDecoder[A]
is isomorphic toKleisli[Xor[Err, ?], Json, A]
, then you could define anIsoFunctor[JsonDecoder, Kleisli[Xor[Err, ?], Json, ?]
and get a derivedMonad
instance that delegates through to theMonad
instance forKleisli
. Defining these instances is very boiler-platey because you have to define eachMonad
method and have it delegate through to the same method in the instance of the isomorphic type. It would be really easy for a new method to be added toMonad
without being added to theisoFunctorMonad
, meaning you would pick up the defaultMonad
implementation of that method and not benefit from any performance optimizations that had been added to the type class instance you are delegating through to. Removing the default implementations from theMonad
trait would mean you would be forced to resolve a compile error if you forgot to add the new method.This approach is going to add a fair amount of boilerplate, but that boilerplate will only exist within cats and other code bases that want to go out of their way to make sure they aren't default implementations with poor performance. I think it's worth it. Are people still on board with going forward with this?
The text was updated successfully, but these errors were encountered: