Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 249
Kevin Meredith Avatar asked Jan 09 '18 18:01

Kevin Meredith


People also ask

What is dependent typing in shapeless?

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.

What is generic programming in shapeless?

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;

How does shapeless handle literal string types?

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

Does shapeless use tuples and either?

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.


1 Answers

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.

like image 105
g.krastev Avatar answered Sep 19 '22 21:09

g.krastev