Open DmytroMitin opened 1 year ago
Just for the context, here is more meaningful example. Repo https://github.com/DmytroMitin/phantom, branch hlisteqs
, commit baf6301344245025af185186eab56ae9cada2e63
https://github.com/outworkers/phantom/pull/934
I define instances of a type class
trait SingleGeneric[T, Store, GenR] {
type Repr = Store
def to(t: T): Repr
def from(r: Repr): T
}
trait LowPrioritySingleGeneric {
implicit def single[T, HL](implicit gen: shapeless.Generic.Aux[T, HL]): SingleGeneric[T, T :: HNil, HL] =
new SingleGeneric[T, T :: HNil, HL] {
def to(source: T): T :: HNil = source :: HNil
def from(hl: T :: HNil): T = hl.head
}
}
object SingleGeneric extends LowPrioritySingleGeneric {
implicit def generic[T, HL](implicit gen: shapeless.Generic.Aux[T, HL]): SingleGeneric[T, HL, HL] =
new SingleGeneric[T, HL, HL] {
def to(source: T): HL = gen to source
def from(hl: HL): T = gen from hl
}
}
but if I remove the extra type parameter in instance declarations and use path-dependent type instead then the project will not compile (Scala 2.13)
trait LowPrioritySingleGeneric {
implicit def single[T](implicit gen: Generic[T]): SingleGeneric[T, T :: HNil, gen.Repr] =
new SingleGeneric[T, T :: HNil, gen.Repr] {
def to(source: T): T :: HNil = source :: HNil
def from(hl: T :: HNil): T = hl.head
}
}
object SingleGeneric extends LowPrioritySingleGeneric {
implicit def generic[T](implicit gen: Generic[T]): SingleGeneric[T, gen.Repr, gen.Repr] =
new SingleGeneric[T, gen.Repr, gen.Repr] {
def to(source: T): gen.Repr = gen to source
def from(hl: gen.Repr): T = gen from hl
}
}
Here TC1
is SingleGeneric
, TC2
is Generic
. This is kind of code snippet 6 in https://github.com/scala/bug/issues/12767 . In Scala 3 this compiles: https://scastie.scala-lang.org/DmytroMitin/C8ziePLmSfifN2Je2Wsbcw/1
If I split the type class into two then SingleGeneric1
and SingleGeneric2
will be TC1
and TC2
package object macros {
type SingleGeneric[T, Store, GenR] = SingleGeneric1.Aux[T, Store, GenR]
}
trait SingleGeneric1[T] extends Serializable {
type Store
type GenR
type Repr = Store
def to(t: T): Store
def from(s: Store): T
}
object SingleGeneric1 {
type Aux[T, S, R] = SingleGeneric1[T] {type Store = S; type GenR = R}
def instance[T, S, R](f: T => S, g: S => T): Aux[T, S, R] = new SingleGeneric1[T] {
override type Store = S
override type GenR = R
override def to(t: T): Store = f(t)
override def from(s: Store): T = g(s)
}
// the project compiles
implicit def mkSingleGeneric1[T, S](implicit
sgen2: SingleGeneric2.Aux[T, S],
gen: Generic[T]
): Aux[T, S, gen.Repr] = instance(sgen2.to(_), sgen2.from)
// the project doesn't compile
//implicit def mkSingleGeneric1[T](implicit
// sgen2: SingleGeneric2[T],
// gen: Generic[T]
//): Aux[T, sgen2.Store, gen.Repr] = instance(sgen2.to(_), sgen2.from)
}
trait SingleGeneric2[T] extends Serializable {
type Store
def to(t: T): Store
def from(s: Store): T
}
trait LowPrioritySingleGeneric2 {
type Aux[T, S] = SingleGeneric2[T] {type Store = S}
def instance[T, S](f: T => S, g: S => T): Aux[T, S] = new SingleGeneric2[T] {
override type Store = S
override def to(t: T): Store = f(t)
override def from(s: Store): T = g(s)
}
implicit def single[T]: Aux[T, T :: HNil] = instance(_ :: HNil, _.head)
}
object SingleGeneric2 extends LowPrioritySingleGeneric2 {
implicit def generic[T](implicit gen: Generic[T]): Aux[T, gen.Repr] =
instance(gen.to(_), gen.from)
}
In Scala 3 this doesn't compile either: https://scastie.scala-lang.org/DmytroMitin/C8ziePLmSfifN2Je2Wsbcw/6 This is kind of item 3 in the current issue.
Compiler version
3.3.0-RC3
Motivation
I have two type classes. They are working on type level. They accept a type and return a type. The 1st type class delegates to the 2nd (it can do some extra work but for minimality it just delegates). The 2nd type class returns
Int
for any typeT
(higher-priority instance) orString
for any typeT
(lower-priority instance). In a more meaningful example the "return type" is calculated in some way and the higher-priority instance manages the case with additional upper bound or additional type-class constraint but for minimality it's just two similar instances, higher-priority and lower-priority. I make the "return type" of the 1st type class a type member.https://scastie.scala-lang.org/DmytroMitin/MEyLY8TkRfaif5C778wkRw/1
https://scastie.scala-lang.org/DmytroMitin/MEyLY8TkRfaif5C778wkRw/2
Minimized code
https://scastie.scala-lang.org/DmytroMitin/MEyLY8TkRfaif5C778wkRw
Output
Expectation
summon[TC1.Aux[Boolean, String]]
in 3) should compile too.Or is it intended difference between type parameters and type members? (Functional dependencies?)
In Scala 2.13 behavior is the same
But in 2.13 there is easier reproduction (without lower priority) https://github.com/scala/bug/issues/12767