POC: Implement functor/contravariant/invariant for some TCs
I had a need for Functor on ApplicativeAsk, so I just went with it and looked at other possible implementations.
TODO:
- [x] instances for other type classes
- [ ] law tests, obviously
Let me know if this looks like a nice thing to have :)
Codecov Report
Merging #119 into master will decrease coverage by
1.95%. The diff coverage is0%.
@@ Coverage Diff @@
## master #119 +/- ##
==========================================
- Coverage 90.1% 88.14% -1.96%
==========================================
Files 67 67
Lines 586 599 +13
Branches 2 1 -1
==========================================
Hits 528 528
- Misses 58 71 +13
| Impacted Files | Coverage Ξ | |
|---|---|---|
| core/src/main/scala/cats/mtl/MonadState.scala | 61.53% <0%> (-38.47%) |
:arrow_down: |
| core/src/main/scala/cats/mtl/FunctorTell.scala | 55.55% <0%> (-44.45%) |
:arrow_down: |
| core/src/main/scala/cats/mtl/ApplicativeAsk.scala | 71.42% <0%> (-28.58%) |
:arrow_down: |
Continue to review full report at Codecov.
Legend - Click here to learn more
Ξ = absolute <relative> (impact),ΓΈ = not affected,? = missing dataPowered by Codecov. Last update 62bdf5c...ec2ae7c. Read the comment docs.
I can't quite decide if this is nice to have or not. :-D It allows you to derive new instances really easily, but it also feels so bizarre for a typeclass to form an instance of another typeclass.
@djspiewak we have that already! InvariantSemigroupal[Monoid], Contravariant[Show], SemigroupK[Decoder] and so on. I believe that's what makes type classes in Scala cooler than Haskell's.
By that standard then, I'm π on absolutely as much of this as is lawful.
@djspiewak I finally revisited this, had to basically start over because of the new naming/hierarchy.
I added a couple examples of the approach I'd take to law-test these instances - the tough part is the equality definitions of the type classes (Eq[Raise] etc.).
I used the instances of Eq[Semilattice] and friends as inspiration (Eq.by and comparing all the functions in the type class), but that only worked as long as the methods had no type parameters - the moment a type param appears, it becomes more complicated.
For Listen, which has the listen method, which has a type parameter, I tried to do some polymorphic magic with skolems, but that seemed to hang the compiler or something π
so in the end, I went with a less universal approach, adding a new type parameter to the Eq instance function:
implicit def catsEqForListen[F[_], A, E](
implicit eqTell: Eq[A => F[Unit]],
eqListen: Eq[F[E] => F[(E, A)]]): Eq[Listen[F, A]] = {
Eq.by { instance =>
(
instance.tell _,
instance.listen[E] _
)
}
Then it needs another method to forward to this one because E could never be inferred otherwise - but it works. What do you think? If it seems reasonable I'll add the missing instances and tests and hopefully, we can get this merged.
Another question I have is whether these Eq instances should be in the core module (with the type classes they're defined for) or in the laws module, like the Cats instances.
Finally, instances of other TCs embedded as fields (e.g. Functor in Tell). For now, I decided to ignore these but maybe I shouldn't.