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

Interaction between shapeless FieldType, and Scalaz tagged types #309

Closed
alexarchambault opened this issue Jan 11, 2015 · 17 comments
Closed
Assignees
Labels

Comments

@alexarchambault
Copy link
Collaborator

When using at the same time, Lazy, labels, and scalaz tagged types, implicits resolution with Lazy seems to fail:

import scalaz.@@

case class Dummy(i: Int @@ CustomTag)
case class DummyTagged(b: Boolean, i: Int @@ CustomTag)

implicitly[TC[Int @@ CustomTag]]
implicitly[TC[Boolean]]


implicitly[TC[Dummy]] // Works for this one...

type R = Record.`'i -> Int @@ CustomTag`.T
val lgen = LabelledGeneric[Dummy]
implicitly[lgen.Repr =:= R]
implicitly[TC[R]] // ...but not for its record

implicitly[TC[DummyTagged]] // Does *not* work for this one...

type RT = Record.`'b -> Boolean, 'i -> Int @@ CustomTag`.T
val lgent = LabelledGeneric[DummyTagged]
implicitly[lgent.Repr =:= RT]
implicitly[TC[RT]] // ...nor for its record

Full example here: https://gist.github.com/alexarchambault/ebf6ac15fceccbd37fc5#file-tc-scala

(Original reason for using scalaz @@ was scalaz/scalaz#747, which was addressed in scalaz but not in shapeless at some point.)

When using shapeless' @@ type instead of the one of scalaz, one runs into runtime exceptions instead (I'll post an issue about it).

When using Generic instead of LabelledGeneric (like in https://gist.github.com/alexarchambault/ebf6ac15fceccbd37fc5#file-tc-nonlabelled-scala), it works.

@milessabin
Copy link
Owner

I think if it works with Generic but not LabelledGeneric then there's a reasonably good chance that this has a fairly straightforward fix.

@milessabin milessabin added the Bug label Jan 13, 2015
@milessabin milessabin added this to the shapeless-2.1.0 milestone Jan 13, 2015
@milessabin milessabin self-assigned this Jan 13, 2015
@milessabin
Copy link
Owner

This appears to be an interaction between the Scalaz encoding of tagging and shapeless's encoding of fields.

With the following local definition of Scalaz tags,

type Tagged[A, T] = { type Tag = T; type Self = A }
type @@[T, Tag] = Tagged[T, Tag]

we get the following behaviour,

val rec = 'i ->> 23.asInstanceOf[Int @@ CustomTag] :: HNil
rec('i)

[error] /home/miles/projects/shapeless/scratch/src/test/scala/shapeless/labelledgeneric.scala:416: No field Symbol with shapeless.tag.Tagged[String("i")] in record shapeless.::[Object{type Tag = shapeless.ScalazTaggedAux.CustomTag; type Self = Int} with shapeless.labelled.KeyTag[Symbol with shapeless.tag.Tagged[String("i")],Object{type Tag = shapeless.ScalazTaggedAux.CustomTag; type Self = Int}],shapeless.HNil]      
[error]     rec('i)                                                                                                                         
[error]        ^

Nevertheless, selecting for the whole field works as expected,

rec.select[FieldType[Witness.`'i`.T, Int @@ CustomTag]]

I'm not 100% sure what's happening here, but it seems that the two refinements (the one that encodes the shapeless field type, and the one that encodes the Scalaz tag) are interfering with inference for the relevant implicits.

There is a workaround for scenarios like yours though. If you add an additional case that explicitly accommodates Scalaz tags then you get the expected results,

implicit def hconsTC0[K <: Symbol, H, HT, T <: HList](implicit
  key: Witness.Aux[K],
  headTC: Lazy[TC[H @@ HT]],
  tailTC: Lazy[TC[T]],
): TC[FieldType[K, H @@ HT] :: T] =
  new TC[FieldType[K, H @@ HT] :: T] {
    def apply() = s"${key.value.name}: ${headTC.value()} :: ${tailTC.value()}"
  }

Presumably here the additional structure in the types being resolved allows inference to succeed.

Given that this works as originally written with shapeless rather than Scalaz tags, and that we have a workaround for Scalaz tags I'm going to park this for now and maybe revisit it after the 2.1.0 release.

@milessabin milessabin changed the title Interaction between Lazy, labels, and scalaz tagged types ? Interaction between shapeless FieldType, and Scalaz tagged types Jan 14, 2015
@alexarchambault
Copy link
Collaborator Author

Thanks for the hint, didn't realize it was more general than Lazy.
I'm using this in https://github.com/alexarchambault/case-app. Your workaround should work for it.

I'd be happy to switch to shapeless' tags by the way, but I ran into #310.

@milessabin
Copy link
Owner

Fix for #310 about to be pushed :-)

@torgeirsh
Copy link

The workaround compiles for me without code duplication if the implementation for the tagged hcons just calls the untagged hcons.

@Astrac
Copy link

Astrac commented Apr 20, 2016

Is this a problem with scalaz tagged types only? I am experiencing the same issue with shapeless @@ as well, maybe if that's the case the ticket should be renamed so that people having this issue with any tagged type implementation can know that it is well known.

@milessabin
Copy link
Owner

It is supposed to only be a problem with Scalaz tagged types. If you have a reproduction with shapeless tagged types would you mind posting it here?

@Astrac
Copy link

Astrac commented Apr 26, 2016

This is the code that is failing:

implicit def taggedReadable[A, T, M[_, _]](
  implicit
  r: Lazy[Readable[A]],
  ev: @@[A, T] =:= M[A, T]
): Readable[M[A, T]] = new Readable[M[A, T]] {}

type CustomerTag = W.`'Customer`.T
type CustomerId = String @@ CustomerTag

case class Customer(id: CustomerId, name: String)

implicitly[Readable[CustomerId]]
implicitly[Readable[Customer]]

sealed trait BookTag
type BookId = String @@ BookTag

case class Book(id: BookId, title: String)
implicitly[Readable[BookId]]
implicitly[Readable[Book]]

I can't derive an instance of my Readable typeclass for neither of Book or Customer. If I replace the tagged type with the primitive one (i.e. String) it works instead; the instance for the tagged types themselves is OK.

It is worth mentioning that the Readable derivation depends on circe's auto-derivation of Decoder, which may be the bit that is not working properly. I'll try to re-create this without circe involved.

@milessabin
Copy link
Owner

@Astrac something standalone would be helpful.

@Astrac
Copy link

Astrac commented Apr 26, 2016

This is a standalone version of the problem:

sealed trait Readable[T]

object Readable {
  implicit def taggedReadable[A, T, M[_, _]](
    implicit
    r: Lazy[Readable[A]],
    ev: @@[A, T] =:= M[A, T]
  ): Readable[M[A, T]] = ???

  implicit val stringReadable: Readable[String] = ???

  implicit val hnilReadable: Readable[shapeless.HNil] = ???

  implicit def hconsReadable[H, T <: HList](
    implicit
    rh: Lazy[Readable[H]],
    rt: Readable[T]
  ): Readable[H :: T] = ???

  implicit def genericReadable[T, TGen <: HList](
    implicit
    gen: shapeless.Generic.Aux[T, TGen],
    genR: Readable[TGen]
  ): Readable[T] = ???
}

type CustomerTag = W.`'Customer`.T
type CustomerId = String @@ CustomerTag

case class Customer(id: CustomerId, name: String)

implicitly[Readable[CustomerId]]
implicitly[Readable[Customer]]

sealed trait BookTag
type BookId = String @@ BookTag

case class Book(id: BookId, title: String)

implicitly[Readable[BookId]]
implicitly[Readable[Book]]

In this case the derivation fails for both Customer and Book. Interestingly enough removing the Lazy in hconsReadable it compiles; in this case I could just remove it but when the derivation is used in third party libraries that won't compile without Lazy this could be a problem.

@milessabin
Copy link
Owner

OK, thanks. I can reproduce the problem. It's different from the one in this ticket though ... it seems to be an interaction between Lazy and tagged types. Would you mind creating a fresh ticket for it with your example?

@Astrac
Copy link

Astrac commented Apr 26, 2016

No problems, thanks!

@milessabin milessabin removed this from the shapeless-2.3.1 milestone May 9, 2016
@joroKr21
Copy link
Collaborator

joroKr21 commented Sep 9, 2017

This is surely a Scala bug:

type With[A, T] = A with T
type Struct = { def x: Int }
trait Trait   { def x: Int }

class TC[T]
implicit def tc[A, T]: TC[A With T] = new TC

implicitly[TC[Trait  With Serializable]] // ok
implicitly[TC[Trait  with Serializable]] // ok
implicitly[TC[Struct With Serializable]] // ok
implicitly[TC[Struct with Serializable]] // nope

-Xlog-implicits reports:

Information:(14, 13) tc is not a valid implicit value for TaggedTypes.TC[TaggedTypes.Struct with Serializable] because:
hasMatchingSymbol reported error: polymorphic expression cannot be instantiated to expected type;
 found   : [A, T]TaggedTypes.TC[TaggedTypes.With[A,T]]
    (which expands to)  [A, T]TaggedTypes.TC[A with T]
 required: TaggedTypes.TC[TaggedTypes.Struct with Serializable]
    (which expands to)  TaggedTypes.TC[AnyRef{def x: Int} with Serializable]
  implicitly[TC[Struct with Serializable]] // nope

@milessabin
Copy link
Owner

@joroKr21 interesting. Yes that does look like a scalac bug. Would you mind creating a ticket for it in scala/bug and I'll take a look.

@joroKr21
Copy link
Collaborator

@milessabin scala/bug#10506

@joroKr21 joroKr21 self-assigned this Mar 16, 2020
@joroKr21 joroKr21 modified the milestones: shapeless-2.4.0, shapeless-2.5.0 Mar 16, 2020
@joroKr21
Copy link
Collaborator

joroKr21 commented Feb 11, 2022

FTR Scalaz now uses a better encoding of tagged types based on abstract types which doesn't have this issue

@joroKr21 joroKr21 removed this from the shapeless-2.5.0 milestone Sep 12, 2022
@joroKr21
Copy link
Collaborator

This issue is mitigated in Scalaz and at this point there is nothing more we can do here

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

Successfully merging a pull request may close this issue.

5 participants