Given the following on Ammonite:
@ import $ivy.`io.circe::circe-core:0.9.0`
@ import $ivy.`io.circe::circe-generic:0.9.0`
@ import $ivy.`com.chuusai::shapeless:2.3.3`
@ import shapeless.tag
import shapeless.tag
@ trait Foo
defined trait Foo
@ import io.circe._, io.circe.generic.semiauto._
import io.circe._, io.circe.generic.semiauto._
@ import shapeless.tag.@@
import shapeless.tag.@@
Then, I attempted to define a generic tagged type decoder:
@ implicit def taggedTypeDecoder[A, B](implicit ev: Decoder[A]): Decoder[A @@ B] =
ev.map(tag[B][A](_))
defined function taggedTypeDecoder
It works when explicitly spelling out String @@ Foo
:
@ val x: String @@ Foo = tag[Foo][String]("foo")
x: String @@ Foo = "foo"
@ implicitly[Decoder[String @@ Foo]]
res10: Decoder[String @@ Foo] = io.circe.Decoder$$anon$21@2b17bb37
But, when defining a type alias:
@ type FooTypeAlias = String @@ Foo
defined type FooTypeAlias
It's not compiling:
@ implicitly[Decoder[FooTypeAlias]]
cmd12.sc:1: diverging implicit expansion for type io.circe.Decoder[ammonite.$sess.cmd11.FooTypeAlias]
starting with method decodeTraversable in object Decoder
val res12 = implicitly[Decoder[FooTypeAlias]]
^
Compilation Failed
Why is that? Is there a known "fix?"
When coding with shapeless, we are often trying to find a target type that depends on values in our code. This relationship is called dependent typing. Problems involving dependent types can be conveniently expressed using implicit search, allowing the compiler to resolve intermediate and target types given a starting point at the call site.
2Algebraic data types and generic representations The main idea behind generic programming is to solve problems for a wide variety of types by writing a small amount of generic code. Shapeless provides two sets of tools to this end: a set of generic data types that can be inspected, traversed, and manipulated at the type level;
Instead of representing the field names with literal string types, shapeless is representing them with symbols tagged with literal string types. The details of the implementation aren’t particularly important: we can still use Witnessand FieldTypeto extract the tags, but they come out as Symbolsinstead of Strings7. 5.3.1Instances for HLists
However, instead of using Tuplesand Either, shapeless uses its own data types to represent generic products and coproducts. We’ll introduce these types in the next sections.
Lucky you, to hit two compiler bugs in the same day. This one is scala/bug#8740. The good? news is that there is a partial fix waiting around in this comment for someone to step up and make a PR (maybe this is you). I believe it's partial because it looks like it will work for a specific tag but not for a generic one (I'm not 100% sure).
The reason why you see a diverging implicit expansion is really funny. The compiler can either expand all aliases in one step (essentially going from FooTypeAlias |= String with Tagged[Foo]
) or not expand anything. So when it compares String @@ Foo
and A @@ B
it doesn't expand, because they match as is. But when it compares FooTypeAlias
and A @@ B
it expands both fully and it ends up in a situation where it has to compare refined types one of which contains type variables (see my answer to your other related question). Here our carefully crafted abstractions break down again and the order of constraints starts to matter. You as the programmer, looking at A with Tagged[B] <:< String with Tagged[Foo]
know that the best match is A =:= String
and B =:= Foo
. However Scala will first compare A <:< String
and A <:< Tagged[Foo]
and it concludes that A <:< Tagged[Foo] with String
(yes, in reverse) which leaves Nothing
for B
. But wait, we need an implicit Decoder[A]
! - which sends us in a loop. So A
got over-constrained and B
got under-constrained.
Edit: It seems to work if we make @@
abstract in order to prevent the compiler from dealiasing: milessabin/shapeless#807. But now it boxes and I can't make arrays work.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With