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

Problems deriving typeclass instances for types with type-members #868

Closed
ryan-williams opened this issue Oct 12, 2018 · 7 comments
Closed
Assignees
Labels

Comments

@ryan-williams
Copy link

ryan-williams commented Oct 12, 2018

I thought the[T]the[Lazy[T]], but here is a counterexample:

trait TC[T]  // example typeclass

 trait B { type T[_] }  // type with an HKT-member
object B { type Aux[_T[_]] = B { type T[U] = _T[U] } }  // "Aux" alias

implicit def bt[T[_]]: TC[B.Aux[T]] = ???

the[     TC[B.Aux[List]] ]  // ✅ works as expected
the[Lazy[TC[B.Aux[List]]]]  // 🚫 wrapping in `Lazy` breaks the summon!

scalafiddle

Digging further, there are a few issues deriving typeclass-instances (choosing from {Generic, LabelledGeneric} x {Lazy, non-Lazy}) for "Aux"-style aliases (where the type-member being "Aux"'d may be an HKT or not):

import shapeless._, labelled.FieldType

trait LL[T]  // typeclass with Lazy LabelledGeneric derivations
implicit val hnilLL: LL[HNil] = ???
implicit def consLL[K <: Symbol, H, T <: HList](implicit w: Witness.Aux[K], h: Lazy[LL[H]], t: Lazy[LL[T]]): LL[FieldType[K, H] :: T] = ???
implicit def ccLL[CC, L <: HList](implicit g: LabelledGeneric.Aux[CC, L], l: Lazy[LL[L]]): LL[CC] = ???

trait LG[T]  // typeclass with Lazy Generic derivations
implicit val hnilLG: LG[HNil] = ???
implicit def consLG[H, T <: HList](implicit h: Lazy[LG[H]], t: Lazy[LG[T]]): LG[H :: T] = ???
implicit def ccLG[CC, L <: HList](implicit g: Generic.Aux[CC, L], l: Lazy[LG[L]]): LG[CC] = ???

trait EL[T]  // typeclass with non-Lazy ("eager") LabelledGeneric derivations
implicit val hnilEL: EL[HNil] = ???
implicit def consEL[K <: Symbol, H, T <: HList](implicit w: Witness.Aux[K], h: EL[H], t: EL[T]): EL[FieldType[K, H] :: T] = ???
implicit def ccEL[CC, L <: HList](implicit g: LabelledGeneric.Aux[CC, L], l: EL[L]): EL[CC] = ???

trait EG[T]  // typeclass with non-Lazy ("eager") Generic derivations
implicit val hnilEG: EG[HNil] = ???
implicit def consEG[H, T <: HList](implicit h: EG[H], t: EG[T]): EG[H :: T] = ???
implicit def ccEG[CC, L <: HList](implicit g: Generic.Aux[CC, L], l: EG[L]): EG[CC] = ???

// tests on a type with a simple type-member:
 trait A { type T }
object A { type Aux[_T] = A { type T = _T } }  // "Aux" alias
case class A1[T](a1: A.Aux[T], a2: A.Aux[T])
case class A2[T](a1: A1   [T])

implicit def a1[T]: LL[A.Aux[T]] = ???
implicit def a2[T]: LG[A.Aux[T]] = ???
implicit def a3[T]: EL[A.Aux[T]] = ???
implicit def a4[T]: EG[A.Aux[T]] = ???

the[     LL[A.Aux[Int] ]]  //
the[     LG[A.Aux[Int] ]]  //
the[     EL[A.Aux[Int] ]]  //
the[     EG[A.Aux[Int] ]]  //

the[Lazy[LL[A.Aux[Int]]]]  //
the[Lazy[LG[A.Aux[Int]]]]  //
the[Lazy[EG[A.Aux[Int]]]]  //
the[Lazy[EL[A.Aux[Int]]]]  //

the[     LL[A1   [Int] ]]  // 🚫
the[     LG[A1   [Int] ]]  //
the[     EL[A1   [Int] ]]  //
the[     EG[A1   [Int] ]]  //

the[     LL[A2   [Int] ]]  // 🚫
the[     LG[A2   [Int] ]]  //
the[     EG[A2   [Int] ]]  // 🚫 this is expected: non-lazy derivation
the[     EL[A2   [Int] ]]  // 🚫 this is expected: non-lazy derivation

// tests on a type with a an HKT-member:
 trait B { type T[_] }
object B { type Aux[_T[_]] = B { type T[U] = _T[U] } }  // "Aux" alias
case class B1[T[_]](b1: B.Aux[T], b2: B.Aux[T])
case class B2[T[_]](b1: B1   [T])

implicit def b1[T[_]]: LL[B.Aux[T]] = ???
implicit def b2[T[_]]: EL[B.Aux[T]] = ???
implicit def b3[T[_]]: LG[B.Aux[T]] = ???
implicit def b4[T[_]]: EG[B.Aux[T]] = ???

the[     LL[B.Aux[List]] ]  //
the[     LG[B.Aux[List]] ]  //
the[     EL[B.Aux[List]] ]  //
the[     EG[B.Aux[List]] ]  //

the[Lazy[LL[B.Aux[List]]]]  // 🚫
the[Lazy[LG[B.Aux[List]]]]  // 🚫
the[Lazy[EL[B.Aux[List]]]]  // 🚫
the[Lazy[EG[B.Aux[List]]]]  // 🚫

the[     LL[B1   [List]] ]  // 🚫
the[     LG[B1   [List]] ]  // 🚫
the[     EL[B1   [List]] ]  //
the[     EG[B1   [List]] ]  //

the[     LL[B2   [List]] ]  // 🚫
the[     LG[B2   [List]] ]  // 🚫
the[     EL[B2   [List]] ]  // 🚫 this is expected: non-lazy derivation
the[     EG[B2   [List]] ]  // 🚫 this is expected: non-lazy derivation

scalafiddle

All the 🚫's should compile but don't, afaict (expect the ones I marked as "expected").

The simplest description of this data I can come up with is: "derivations of types with type-members only work if the type-member is a non-HKT and the derivation is non-'labelled'".

Put another way: LabelledGeneric derivations in the presence of a type-member – and all derivations if it's an HKT-member – don't work.

However, even these descriptions miss some subtleties about the how far the different cases get before they stop working.

This looks similar to #584, but that seems explicitly focused on tagged types; I'm not sure if #797 also fixes any/all of these.

@joroKr21
Copy link
Collaborator

Have you tried with latest scala 2.13?

@ryan-williams
Copy link
Author

I quickly tried it with 2.13.0-M5 here; the results are the same except that the 4 annotated with "this is expected: non-lazy derivation" above now pass!

Is that what you expected? Is Lazy not necessary in 2.13.0-M5, and so all my derivations will now work if I remove it?

@ryan-williams
Copy link
Author

(aside: using shapeless 2.4.0-SNAPSHOT from current HEAD doesn't change things in 2.12 or 2.13.0-M5)

@milessabin
Copy link
Owner

By-name implicits are a replacement for Lazy. The only caveat is that by-name parameters aren't stable so you'll have to use the either the Aux pattern, which can be problematic with HKTs.

I'm also wondering if you might get further with (Labelled)Generic1 or, failing that, at least using its continuation passing style alternative to attempting to extract higher kinded types from instances.

@joroKr21
Copy link
Collaborator

#797 makes all examples compile, except the expected ones.

@ryan-williams
Copy link
Author

very cool, @joroKr21, thanks! hopefully it makes it in to 2.4.0 / soon. maybe I will experiment with using a fork that includes it.

@milessabin interesting! I've looked at Generic1 but don't really get it, or see how I could use it here. if there is documentation or an explanation (beyond the Functor example) I'd be interested to keep trying to learn. Also I haven't been able to find LabelledGeneric1.

@joroKr21
Copy link
Collaborator

Fixed by #797

@joroKr21 joroKr21 added this to the shapeless-2.4.0 milestone Mar 16, 2020
@joroKr21 joroKr21 added the Bug label Mar 16, 2020
@joroKr21 joroKr21 self-assigned this Mar 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants