cats-mtl icon indicating copy to clipboard operation
cats-mtl copied to clipboard

POC: Implement functor/contravariant/invariant for some TCs

Open kubukoz opened this issue 6 years ago β€’ 5 comments

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 :)

kubukoz avatar Jun 17 '19 16:06 kubukoz

Codecov Report

Merging #119 into master will decrease coverage by 1.95%. The diff coverage is 0%.

Impacted file tree graph

@@            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 data Powered by Codecov. Last update 62bdf5c...ec2ae7c. Read the comment docs.

codecov-io avatar Jun 17 '19 17:06 codecov-io

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 avatar Sep 10 '19 22:09 djspiewak

@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.

kubukoz avatar Sep 12 '19 22:09 kubukoz

By that standard then, I'm πŸ‘ on absolutely as much of this as is lawful.

djspiewak avatar Sep 12 '19 22:09 djspiewak

@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.

kubukoz avatar Jan 06 '21 14:01 kubukoz