简体   繁体   中英

Decoding Shapeless Tagged Types

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?"

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM