Type Class Derivation Does Not Work With Contravariant Types
See original GitHub issueCompiler version
3.0.1
Minimized code
import scala.deriving.*
import scala.compiletime.{erasedValue, summonInline}
inline def summonAll[T <: Tuple]: List[Eq[_]] =
inline erasedValue[T] match
case _: EmptyTuple => Nil
case _: (t *: ts) => summonInline[Eq[t]] :: summonAll[ts]
trait Eq[-T]:
def eqv(x: T, y: T): Boolean
object Eq:
given Eq[Int] with
def eqv(x: Int, y: Int) = x == y
def check(elem: Eq[_])(x: Any, y: Any): Boolean =
elem.asInstanceOf[Eq[Any]].eqv(x, y)
def iterator[T](p: T) = p.asInstanceOf[Product].productIterator
def eqSum[T](s: Mirror.SumOf[T], elems: => List[Eq[_]]): Eq[T] =
new Eq[T]:
def eqv(x: T, y: T): Boolean =
val ordx = s.ordinal(x)
(s.ordinal(y) == ordx) && check(elems(ordx))(x, y)
def eqProduct[T](p: Mirror.ProductOf[T], elems: => List[Eq[_]]): Eq[T] =
new Eq[T]:
def eqv(x: T, y: T): Boolean =
iterator(x).zip(iterator(y)).zip(elems.iterator).forall {
case ((x, y), elem) => check(elem)(x, y)
}
inline given derived[T](using m: Mirror.Of[T]): Eq[T] =
lazy val elemInstances = summonAll[m.MirroredElemTypes]
inline m match
case s: Mirror.SumOf[T] => eqSum(s, elemInstances)
case p: Mirror.ProductOf[T] => eqProduct(p, elemInstances)
end Eq
enum Opt[+T] derives Eq:
case Sm(t: T)
case Nn
@main def test(): Unit =
import Opt.*
val eqoi = summon[Eq[Opt[Int]]]
assert(eqoi.eqv(Sm(23), Sm(23)))
assert(!eqoi.eqv(Sm(23), Sm(13)))
assert(!eqoi.eqv(Sm(23), Nn))
Output
no implicit argument of type deriving.Mirror.Of[T] was found for parameter m of given instance derived in object Eq
where: T is a type variable with constraint >: Opt[T]
Expectation
This example from the documentation does not work when Eq
is contravariant instead of invariant.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:5
- Comments:11 (9 by maintainers)
Top Results From Across the Web
Why doesn't the example compile, aka how does (co-, contra ...
I understand the distinction between +T and T in the type declaration (it compiles if I use T ). But then how does...
Read more >Covariance and Contravariance in Generics - Microsoft Learn
However, variance in delegate binding works with all delegate types, not just with generic delegate types that have variant type parameters.
Read more >Mirrors and variance - Question - Scala Users
So this works: trait Foo[A] object Foo: given [A <: Product](using m: ... Type Class Derivation Does Not Work With Contravariant Types.
Read more >Understanding Covariance and Contravariance of Generic ...
We will explain what it means for a generic type to be variant, are and how do we use variance when constructing and...
Read more >Covariance and contravariance (computer science) - Wikipedia
InterfacesEdit · Non-generic types (classes, structs, enums, etc.) · A type parameter T is valid covariantly if it was not marked in ,...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
@odersky I’m not sure what you mean when you say “
Eq
is invariant anyway”.Eq
is not defined in the Scala standard library. It is defined as invariant in some libraries and is defined as contravariant in other libraries, for example here, along with a variety of other functional abstractions.Furthermore, I would argue that this reflects the natural variance of this data type. It only accepts
A
values as inputs and never produces them. Covariance and contravariance are two sides of the same coin so seems like a significant limitation that it would work for one and not the other.Would love to use this feature to support derivation of functional abstractions in ZIO Prelude if we can fix this.
after a lot of experimentation I have come up with a good way to break the recursive implicit search, while being efficient with generated code:
This means I can go ahead with a PR to fix the mirror synthesis