Skip to content
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

Add higher kinded versions of Eq, Order, Show #2308

Closed
kubukoz opened this issue Jun 27, 2018 · 10 comments
Closed

Add higher kinded versions of Eq, Order, Show #2308

kubukoz opened this issue Jun 27, 2018 · 10 comments

Comments

@kubukoz
Copy link
Member

kubukoz commented Jun 27, 2018

...or at least some of them.

Reasoning: in #2300, I was trying to implement Eq[Cofree[S, A]] using implicit instances of Eq[S[Cofree[S, A]]] and Eq[A]. That would look like this:

implicit def catsFreeEqForCofree[S[_], A : Eq](implicit S: Eq[S[Cofree[S, A]]]): Eq[Cofree[S, A]] = new Eq[Cofree[S, A]] {
  override def eqv(x: Cofree[S, A], y: Cofree[S, A]): Boolean = Eq[A].eqv(x.head, y.head) && S.eqv(x.tailForced, y.tailForced)
}

However, any attempt at e.g. Eq[Cofree[List, Int]] will fail compilation due to an diverging implicit expansion - and it makes sense, since Eq[List[Cofree[List, A]]] needs an Eq[Cofree[List, A]], which is exactly what we're trying to construct.

The same situation will happen if someone tries to implement Order or Show.

Since we don't have a mechanism for shapeless-style lazy implicits, it doesn't seem possible to implement such instances without changes outside of Cofree.

One solution to this problem that I see is creating a higher kinded variant of Eq, similar to Semigroup / SemigroupK:

trait EqK[F[_]] { self =>
  def eqv[A : Eq](x: F[A], y: F[A]): Boolean

  def algebra[A : Eq]: Eq[F[A]] = new Eq[F[A]] {
    def eqv(x: F[A], y: F[A]): Boolean = self.eqv(x, y)(Eq[A])
  }
}

I believe that's Eq1 in Haskell: http://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Functor-Classes.html#g:2

Having that, we might remove the current instances of Eq for higher kinded types like Option:

implicit def catsKernelStdEqForOption[A: Eq]: Eq[Option[A]] = ...

and replace them with instances of EqK:

implicit val catsKernelStdEqKForOption: EqK[Option] = ...

To make it possible to seamlessly get an Eq[Option[A]] for an A : Eq, we could add a derivation method:

implicit def eqFromEqK[F[_] : EqK, A : Eq]: Eq[F[A]] = EqK[F].algebra[A]

or, alternatively (and this would probably make it possible to keep binary compatibility), add instances of EqK alongside the old instances, and make the old instances use the implementations from EqK instances underneath (the old instances could be removed later, at a point when we can break compatibility).

The same thing could happen to Order and Show.

Another idea would be to introduce lazy implicits to Cats.

@kubukoz
Copy link
Member Author

kubukoz commented Jun 27, 2018

Initial discussion about the old EqK and drawbacks:

https://gitter.im/typelevel/cats?at=5b33fa45479ca26689854ba6

Looks like the reason to remove EqK originally was to loosen the requirements for tests - in places where an EqK[F] and an Eq[A] was required, the removal made it just a requirement on Eq[F[A]].

I believe we could leave these tests unchanged and still have EqK in the library.

This affects instances for any fixpoint data type like Cofree.
An alternative solution is what matryoshka did: simulating lazy implicits using Delay[Eq, F] -

https://github.com/slamdata/matryoshka/blob/2233e287fab4ab8cd509663f2f384822af2ff32c/core/shared/src/main/scala/matryoshka/Delay.scala#L24

@kubukoz
Copy link
Member Author

kubukoz commented Jun 28, 2018

Also, maybe we don't need to support binary type constructors as well - the usecase in which I'm hitting this wall is S[_], so it can't be a type of an arbitrary kind. If S[_] were Either[Int, ?] or Either[?, Int], we should be able to define an EqK for both of these variants.

@djspiewak
Copy link
Member

djspiewak commented Jul 5, 2018

I'm relatively certain Defer solves this problem.

Edit: No it doesn't. I was confused. Delay from Matryoshka does solve it, but Defer as referenced in #2279 does not.

@kubukoz kubukoz mentioned this issue Jul 7, 2018
@kubukoz
Copy link
Member Author

kubukoz commented Jul 7, 2018

I prepared a POC for solving it with Delay (which has its own problems), for anyone interested in the issue: #2316

@xuwei-k
Copy link
Contributor

xuwei-k commented Jul 8, 2018

@kubukoz
Copy link
Member Author

kubukoz commented Jul 10, 2018

I don't like the idea that having Eq for Cofree would require another dependency, let alone shapeless (if we were to implement instances for cats simpilar to what you did in that PR for scalaz)... but if there's a decision that we don't want to add Delay in some shape of form, that might be the best option indeed

@xuwei-k
Copy link
Contributor

xuwei-k commented Jul 10, 2018

https://gist.github.com/xuwei-k/118230f49c6522bddfcf1ef137c11945 byname implicit (since Scala 2.13)

@milessabin
Copy link
Member

You definitely don't want to be using Lazy at this point ...

@kubukoz
Copy link
Member Author

kubukoz commented Jul 11, 2018

guess we'll just have to wait for 2.13 ;)

@kubukoz
Copy link
Member Author

kubukoz commented Jul 30, 2018

Closing, I suggest to wait with the original issue until 2.13 is available, then we can use lazy implicits to add an instance to cats. I'll post more in the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants