In the following code I try to derive typeclass instances with shapeless. However, in the case of a more complex case class (which is converted to a more complex HList), the compiler gives me a "diverging implicit expansion" even though it does not seem to resolve the same kind of implicit type twice. Maybe I am missing some other rule of the compiler?
(Fiddle: https://scalafiddle.io/sf/WEpnAXN/0)
import shapeless._
trait TC[T]
sealed trait Trait1
case class SimpleClass(a: String) extends Trait1
sealed trait Trait2
case class ComplexClass(a: String, b: String) extends Trait2
object Serialization extends App {
//Instances for HList
implicit val hnilInstance: TC[HNil] = ???
implicit def hconsInstance[H, T <: HList] (implicit t: TC[T]): TC[H :: T] = ???
//Instances for CoProduct
implicit val cnilInstance: TC[CNil] = ???
implicit def cconsInstance[H, T <: Coproduct] (implicit h: TC[H], t: TC[T]): TC[H :+: T] = ???
//Instances for Generic, relying on HNil & HCons
implicit def genericInstance[T, H] (implicit g: Generic.Aux[T, H], t: TC[H]): TC[T] = ???
the[TC[SimpleClass :+: CNil]] //Works
the[TC[Trait1]] //Works
the[TC[ComplexClass :+: CNil]] //Works
the[TC[Trait2]] //Fails with diverging implicit expansion
}
When trying to resolve the[TC[Trait1]] the compiler should do something like that:
TC[Trait1]
Generic[Trait1]
TC[SimpleClass :+: CNil]
TC[SimpleClass]
Generic[SimpleClass]
TC[String :: HNil]
TC[CNil]
which seems to work. However, with the 2-field case class, the compiler fails to do something like this - so I wonder: why do I have to use Lazy here already to make it work?
TC[Trait2]
Generic[Trait2]
TC[ComplexClass :+: CNil]
TC[ComplexClass]
Generic[ComplexClass]
TC[String :: String :: HNil]
TC[CNil]
I have created some fiddle so you can execute the code there directy.
A couple of years ago when I was working through some issues like this I found that the easiest way to figure out what the divergence checker was doing was just to throw some printlns into the compiler and publish it locally. In 2.12 the relevant code is the dominates method here, where we can replace the last line with something like this:
overlaps(dtor1, dted1) && (dtor1 =:= dted1 || {
val dtorC = complexity(dtor1)
val dtedC = complexity(dted1)
val result = dtorC > dtedC
println(if (result) "Dominates:" else "Does not dominate:")
println(s"$dtor (complexity: $dtorC)")
println(s"$dted (complexity: $dtedC)")
println("===========================")
result
})
Then we can sbt publishLocal scalac and try to compile your code:
Dominates:
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7)
TC[shapeless.:+:[ComplexClass,shapeless.CNil]] (complexity: 6)
===========================
The problem here is that we're looking for a TC instance for String :: String :: HNil (the lowest node in your tree), but we have an open search for ComplexClass :+: CNil (three steps up). The compiler thinks that String :: String :: HNil both overlaps and dominates ComplexClass :+: CNil, and it bails out because that looks recursive.
This sounds ridiculous, so we can do an experiment to try to convince ourselves by adding some complexity to the coproduct part and seeing what happens. Let's just add a constructor:
case class Foo(i: Int) extends Trait2
Now everything works fine, and we get this message during compilation:
Does not dominate:
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7)
TC[shapeless.:+:[ComplexClass,shapeless.:+:[Foo,shapeless.CNil]]] (complexity: 9)
So the ComplexClass hlist representation still overlaps the Trait2 coproduct representation, but it doesn't dominate it, since the Trait2 representation (the subject of the open implicit TC search we're worried about) is now more complex.
The checker is clearly being much too paranoid here, and its behavior may change in the future, but for now we're stuck with it. As you note, the most straightforward and fool-proof workaround is to stick a Lazy in there to hide the supposed recursion from the divergence checker.
In this case specifically, though, it looks like just putting the instances in the TC companion object works as well:
import shapeless._
sealed trait Trait1
case class SimpleClass(a: String) extends Trait1
sealed trait Trait2
case class ComplexClass(a: String, b: String) extends Trait2
trait TC[T]
object TC {
//Instances for HList
implicit def hnilInstance: TC[HNil] = ???
implicit def hconsInstance[H, T <: HList](implicit t: TC[T]): TC[H :: T] = ???
//Instances for CoProduct
implicit def cnilInstance: TC[CNil] = ???
implicit def cconsInstance[H, T <: Coproduct](implicit
h: TC[H], t: TC[T]
): TC[H :+: T] = ???
//Instances for Generic, relying on HNil & HCons
implicit def genericInstance[T, H](implicit
g: Generic.Aux[T, H], t: TC[H]
): TC[T] = ???
}
And then:
Does not dominate:
TC[shapeless.::[String,shapeless.::[String,shapeless.HNil]]] (complexity: 7)
TC[shapeless.:+:[ComplexClass,shapeless.CNil]] (complexity: 16)
Why does moving stuff around like this raise the complexity of the coproduct? I have no idea.
Related
So I have a user defined input of keys, and the user is also expected to provide serializers for these keys. I am trying to statically ensure that the serializers provided work with the user provided types. Example user usage would be something like:
case class Keys(key1: String, key2: Int)
val keys = Keys("Foo", 2)
val keyDescriptor = (StringSerializer, IntSerializer)
toSerializedKeys(keys, keyDescriptor)
but I would like something like this to fail at compile time:
val keys = Keys("Foo", 2)
val keyDescriptor = (StringSerializer, StringSerializer)
toSerializedKeys(keys, keyDescriptor)
To abstract over arity and attempt to provide compile time type matching on the keys/serializers I'm trying to use shapeless. My current solution works fine given explicit HLists. But as soon as I try to apply this to generic HLists things fall apart.
Current solution:
Assume we have the following user provided serializers:
trait SerializerFn[T] {
def toBytes(t: T): ByteBuffer
}
object StringSerializerFn extends SerializerFn[String] {
override def toBytes(t: String): ByteBuffer = ???
}
object IntSerializerFn extends SerializerFn[Int] {
override def toBytes(t: Int): ByteBuffer = ???
}
I currently create a type class, helper function, and instances to handle serialization of HLists:
trait Serializer[HA <: HList, HB <: HList] {
def toBufs(keys: HA)(fns: HB): List[ByteBuffer]
}
object Serializer extends SerializerInstances {
def toBufs[HA <: HList, HB <: HList](keys: HA)(fns: HB)(
implicit
serializer: Serializer[HA, HB]
): List[ByteBuffer] = {
serializer.toBufs(keys)(fns)
}
}
trait SerializerInstances {
implicit val hnilSerializer: Serializer[HNil, HNil] =
new Serializer[HNil, HNil] {
override def toBufs(keys: HNil)(fns: HNil): List[ByteBuffer] = Nil
}
implicit def hconsSerializer[H, Fn <: SerializerFn[H], TA <: HList, TB <: HList](
implicit
hconsSerializer: Serializer[TA, TB],
): Serializer[H :: TA, Fn :: TB] =
new Serializer[H :: TA, Fn :: TB] {
override def toBufs(keys: H :: TA)(fns: Fn :: TB): List[ByteBuffer] =
List(fns.head.toBytes(keys.head)) ::: hconsSerializer.toBufs(keys.tail)(fns.tail)
}
}
The static type checking I'm going for here is happening in hconsSerializer where we restrict the head of both HLists to be an H and SerializerFn[H], respectively.
And to test it, this compiles fine:
val testKeys = "3" :: "4" :: HNil
val testFns = StringSerializerFn :: StringSerializerFn :: HNil
val testSerializedKeys = Serializer.toBufs(testKeys)(testFns)
and as expected, this doesn't compile:
val testKeys = "3" :: "4" :: HNil
val testFns = StringSerializerFn :: IntSerializerFn :: HNil
val testSerializedKeys = Serializer.toBufs(testKeys)(testFns)
However, when trying to generalize this (so that we can actually use this in a constructor that we provide to users), things fall apart, (I've omitted the Generic conversion from the user provided Product to HList for brevity):
def toSerializedKeys[ReprA <: HList, ReprB <: HList](
keys: ReprA,
fns: ReprB
): List[ByteBuffer] =
Serializer.toBufs(keys)(fns)
For reference, intellij complains that the implicit Serializer can't be found, and explicitly passing hconsSerializer shows:
Required: Serializer[ ReprA, ReprB]
Found: Serializer[Nothing :: HNil, Nothing :: HNil]
and Scalac says:
[error] found : [H, Fn <: com.twitter.beam.io.manhattan.SerializerFn[H], TA <: shapeless.HList, TB <: shapeless.HList]com.twitter.beam.io.manhattan.Serializer[H :: TA,Fn :: TB]
[error] required: com.twitter.beam.io.manhattan.Serializer[ReprA,ReprB]
What kind of implicit evidence do I need to be able to perform this kind of recursive type checking on generic HLists? Is accomplishing something like this even possible? I'm new to Shapeless.
Also, if comparing HList types like this isn't possible. Another solution could be something that allowed the user to define individual implicit key serializers, instead of a full KeyDescriptor that we convert to an HList, e.g something like:
implicit val intSerializer = IntSerializerFn
implicit val strSerizlizer = StringSerializerFn
However, I tried something like this and then tried mapping over the Keys HList with a Poly1 that takes an implicit SerializerFn, but I ran into similar issues when trying to work with a generic HLists.
If you are using Scala3 (aka dotty), here is a simple solution using stdlib only for your purpose:
import scala.deriving.Mirror.ProductOf as PF
import java.nio.ByteBuffer
trait SerializerFn[T]:
def toBytes(t: T): ByteBuffer
object StringSerializerFn extends SerializerFn[String]:
override def toBytes(t: String): ByteBuffer = ???
object IntSerializerFn extends SerializerFn[Int]:
override def toBytes(t: Int): ByteBuffer = ???
case class SI(key1: String, key2: Int)
case class SII(key1: String, key2: Int, key3: Int)
def toSerializedKeys[K <: Product, S <: Tuple](keys: K, sers: S)
(using pf: PF[K], ev: S <:< Tuple.Map[pf.MirroredElemTypes, SerializerFn]): Unit = {}
// success
toSerializedKeys(SI("a", 1), (StringSerializerFn, IntSerializerFn))
toSerializedKeys(SII("a", 1, 2), (StringSerializerFn, IntSerializerFn, IntSerializerFn))
// compile fail
// toSerializedKeys(SI("a", 1), (StringSerializerFn, StringSerializerFn))
// toSerializedKeys(SII("a", 1, 2), (IntSerializerFn, IntSerializerFn, IntSerializerFn))
I guess you just missed evidence serializer: Serializer[ReprA, ReprB] in
def toSerializedKeys[ReprA <: HList, ReprB <: HList](
keys: ReprA,
fns: ReprB
): List[ByteBuffer] =
Serializer.toBufs(keys)(fns)
So just replace this with
def toSerializedKeys[ReprA <: HList, ReprB <: HList](
keys: ReprA,
fns: ReprB
)(implicit serializer: Serializer[ReprA, ReprB]): List[ByteBuffer] =
Serializer.toBufs(keys)(fns)
Then
case class Keys(key1: String, key2: Int)
val keys = Keys("Foo", 2)
val keyDescriptor = (StringSerializer, IntSerializer)
toSerializedKeys(keys, keyDescriptor)
compiles (as expected) and
val keys = Keys("Foo", 2)
val keyDescriptor = (StringSerializer, StringSerializer)
toSerializedKeys(keys, keyDescriptor)
doesn't (as expected).
Just noticed that Alec advised the same in comments.
Can someone explain me what the difference between these two approaches for typeclass instance derivation (specifically for Option[A])?
1.
trait MyTrait[A] {...}
object MyTrait extends LowPriority {
// instances for primitives
}
trait LowPriority extends LowestPriority {
final implicit def generic[A, H <: HList](
implicit gen: Generic.Aux[A, H],
h: Lazy[MyTrait[H]]
): MyTrait[A] = ???
final implicit val hnil: MyTrait[HNil] = ???
final implicit def product[H, T <: HList](
implicit h: Lazy[MyTrait[H]],
t: Lazy[MyTrait[T]]
): MyTrait[H :: T] = ???
}
// special instances for Options
trait LowestPriority {
implicit def genericOption[A, Repr <: HList](
implicit gen: Generic.Aux[A, Repr],
hEncoder: Lazy[MyTrait[Option[Repr]]]
): MyTrait[Option[A]] = ???
implicit val hnilOption: MyTrait[Option[HNil]] = ???
implicit def productOption1[H, T <: HList](
implicit
head: Lazy[MyTrait[Option[H]]],
tail: Lazy[MyTrait[Option[T]]],
notOption: H <:!< Option[Z] forSome { type Z }
): MyTrait[Option[H :: T]] = ???
implicit def product2[H, T <: HList](
implicit
head: Lazy[MyTrait[Option[H]]],
tail: Lazy[MyTrait[Option[T]]
): MyTrait[Option[Option[H] :: T]] = ???
}
trait MyTrait[A] {...}
object MyTrait extends LowPriority {
// instances for primitives
}
trait LowPriority {
// deriving instances for options from existing non-option instances
final implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] = ??? // <<<----
final implicit def generic[A, H <: HList](
implicit gen: Generic.Aux[A, H],
h: Lazy[MyTrait[H]]
): MyTrait[A] = ???
final implicit val hnil: MyTrait[HNil] = ???
final implicit def product[H, T <: HList](
implicit h: Lazy[MyTrait[H]],
t: Lazy[MyTrait[T]]
): MyTrait[H :: T] = ???
}
I tried both and they worked correctly, but i'm not sure that they will produce the same results for all cases (maybe i've missed something).
Do we really need LowestPriority instances for this?
Am i right if i would say that the first approach gives us just a little bit more flexibility?
I assuming that by "worked correctly" you mean "compiled" or "worked for some simple use case".
Both of your examples deal with generic product types, but not with generic sum types, so there is no risk that e.g. Option[A] could get derived using Some[A] :+: None :+: CNil, which would enforce some ambiguity. So (as far as I can tell) you could write the second version like:
trait MyTrait[A] {...}
object MyTrait extends LowPriority {
// instances for primitives
// deriving instances for options from existing non-option instances
final implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] = ???
}
trait LowPriority {
// <<<----
final implicit def hcons[A, H <: HList](
implicit gen: Generic.Aux[A, H],
h: Lazy[MyTrait[H]]
): MyTrait[A] = ???
final implicit val hnil: MyTrait[HNil] = ???
final implicit def product[H, T <: HList](
implicit h: Lazy[MyTrait[H]],
t: Lazy[MyTrait[T]]
): MyTrait[H :: T] = ???
}
and it would derive things correctly.
But how 1. and 2. differs?
In second version you can derive MyTrait[Option[A]] if you can derive for A, and you can derive for any A which is primitive/option/product - so Option[Option[String]], Option[String] and Option[SomeCaseClass] should all work. It should also work if this SomeCaseClass contains fields which are Options, or other case classes which are Options, etc.
Version 1. is slightly different:
at first you are looking for primitives
then you try to derive for a product (so e.g. Option would not be handled here)
then you do something weird:
genericOption assumes that you created a Option[Repr], and then I guess map it using Repr
in order to build that Repr you take Option[HNil] and prepend types inside Option using productOption, which would break if someone used Option as a field
so you "fix" that by prepending an Option in a special case product2
I guess, you tested that only against case classes, because the first version would not work for:
Option for primitives (Option[String], Option[Int] or whatever you defined as primitive)
nested options (Option[Option[String]])
options for custom defined types which are not case classes but have manually defined instances:
class MyCustomType
object MyCustomType {
implicit val myTrait: MyTrait[MyCustomType]
}
implicitly[Option[MyCustomType]]
For that reason any solution with implicit def forOption[A](implicit instance: MyTrait[A]): MyTrait[Option[A]] is simpler and more bulletproof.
Depending on what you put directly into companion low-priority implicits might be or might not be needed:
if you defined coproducts then manual support for e.g. Option, List, Either could conflict with shapeless derived ones
if you manually implemented MyTrait implicit for some type in its companion object then it would have the same priority as implicits directly in MyTrait - so if it could be derived using shapeless you could have conflicts
For that reason it makes sense to put shapeless implicits in LowPriorityImplicits but primitives, and manual codecs for List, Option, Either, etc directly in companion. That is, unless you defined some e.g. Option[String] implicits directly in companion which could clash with "Option[A] with implicit for A".
Since I don't know your exact use case I cannot tell for sure, but I would probably go with the seconds approach, or most likely with the one I implemented in the snippet above.
Actually it's hard to say without right hand sides and actual implementations.
From information you provided it doesn't follow that the two type classes behave equivalently.
For example in the 1st approach you consider some special cases, so theoretically it's possible that you redefine some general behavior in special case differently.
By the way, Option[A] is a coproduct of Some[A] and None.type (List[A] is a coproduct of scala.::[A] and Nil.type) and sometimes it's easier to derive a type class for coproducts than for Option[A] (or List[A]).
I want to derive instances of type classes from unary case classes. But when i try to implicitly derive it i always get an error message. If i derive it explicitly using implicit method - it works. I'm not sure, but maybe the reason is that i missed some implicit types in my function
import shapeless._
import scala.reflect.ClassTag
import scala.reflect.runtime.universe.TypeTag
sealed trait Foo[A] {
def hello(): Unit
}
object Foo {
def apply[A](implicit foo: Foo[A]): foo.type = foo
def instance[A](implicit tag: ClassTag[A]): Foo[A] = new Foo[A] {
override def hello(): Unit = println(s"Hello from ${tag.runtimeClass.getName}")
}
}
trait Instances extends LowestPriority {
implicit val intHelloInstance: Foo[Int] = Foo.instance[Int]
}
trait LowestPriority {
implicit def derive[A: TypeTag, L <: HList, H](
implicit gen: Generic.Aux[A, L],
H: Lazy[Foo[H]],
isUnary: (H :: HNil) =:= L
): Foo[A] =
new Foo[A] {
override def hello(): Unit = {
print(s"Derived: ")
H.value.hello()
}
}
}
object T extends Instances {
case class A(a: Int)
def main(args: Array[String]): Unit = {
intHelloInstance.hello()
// val a: Foo[A] = derive[A, Int :: HNil, Int] // this works
// a.hello()
Foo[A].hello() // error
}
}
From logs:
Information:(45, 8) shapeless.this.Generic.materialize is not a valid
implicit value for shapeless.Generic.Aux[H,L] because:
hasMatchingSymbol reported error: H is not a case class, case
class-like, a sealed trait or Unit
Foo[A].hello()
How can i fix it?
This is one of the cases when behavior depends on... the order of implicits to resolve.
If you modify signature to:
implicit def derive[A, L <: HList, H](
implicit gen: Generic.Aux[A, L],
isUnary: (H :: HNil) =:= L, // swapped
H: Lazy[Foo[H]] // with this
): Foo[A] = ...
compiler will:
try to find some HList L that could be paired with A
then prove that L equal to some H :: HNil figuring out H in the process
finally using that H to fetch Lazy[Foo[H]]
and you will successfully compile Foo[A].hello().
When these two last argument are swapped to what you have in your question, compiler has to
assume some H (which in our test case most likely WON"T be Int)
then forcefully try to adjust L to match it
then go back to Generic which now is forced to prove that A is representable by some H :: HNil where H is most likely not Int and failing to do it but with a misguiding error information
This is one of these cases that shows that shapeless based derivation is sometimes tricky, and also here it shows that shapeless authors assumed in macros that the most likely cause of macro expansion failure is that A was not case class, while as we just saw it might be also compiler forcing some impossible proof because type inference got things wrong because we resolved them in wrong order.
I am trying to solve [this][1] question using Shapeless, in summary it's about converting a nested case class to Map[String,Any], here is the example:
case class Person(name:String, address:Address)
case class Address(street:String, zip:Int)
val p = Person("Tom", Address("Jefferson st", 10000))
The goal is to convert p to following:
Map("name" -> "Tom", "address" -> Map("street" -> "Jefferson st", "zip" -> 10000))
I am trying to do it using Shapeless LabelledGeneric, here is what I have so far:
import shapeless._
import record._, syntax.singleton._
import ops.record._
import shapeless.ops.record._
def writer[T,A<:HList,H<:HList](t:T)
(implicit lGeneric:LabelledGeneric.Aux[T,A],
kys:Keys.Aux[A,H],
vls:Values[A]) = {
val tGen = lGeneric.to(t)
val keys = Keys[lGeneric.Repr].apply
val values = Values[lGeneric.Repr].apply(tGen)
println(keys)
println(values)
}
I am trying to have a recursive writer check each value and try to make Map for each element in value. The above code works fine but when I want to iterate over values with a sample Poly, using the following code I got these errors.
values.map(identity)
//or
tGen.map(identity)
Error:(75, 19) could not find implicit value for parameter mapper: shapeless.ops.hlist.FlatMapper[shapeless.poly.identity.type,vls.Out]
values.flatMap(identity)
^
Error:(75, 19) not enough arguments for method flatMap: (implicit mapper: shapeless.ops.hlist.FlatMapper[shapeless.poly.identity.type,vls.Out])mapper.Out.
Unspecified value parameter mapper.
values.flatMap(identity)
^
I don't know why I'm getting that error. I also would be happy to know if there is an easier way to do the whole thing using Shapeless.
[1]: Scala macros for nested case classes to Map and other way around
Any time you want to perform an operation like flatMap on an HList whose type isn't statically known, you'll need to provide evidence (in the form of an implicit parameter) that the operation is actually available for that type. This is why the compiler is complaining about missing FlatMapper instances—it doesn't know how to flatMap(identity) over an arbitrary HList without them.
A cleaner way to accomplish this kind of thing would be to define a custom type class. Shapeless already provides a ToMap type class for records, and we can take it as a starting point, although it doesn't provide exactly what you're looking for (it doesn't work recursively on nested case classes).
We can write something like the following:
import shapeless._, labelled.FieldType, record._
trait ToMapRec[L <: HList] { def apply(l: L): Map[String, Any] }
Now we need to provide instances for three cases. The first case is the base case—the empty record—and it's handled by hnilToMapRec below.
The second case is the case where we know how to convert the tail of the record, and we know that the head is something that we can also recursively convert (hconsToMapRec0 here).
The final case is similar, but for heads that don't have ToMapRec instances (hconsToMapRec1). Note that we need to use a LowPriority trait to make sure that this instance is prioritized properly with respect to hconsToMapRec0—if we didn't, the two would have the same priority and we'd get errors about ambiguous instances.
trait LowPriorityToMapRec {
implicit def hconsToMapRec1[K <: Symbol, V, T <: HList](implicit
wit: Witness.Aux[K],
tmrT: ToMapRec[T]
): ToMapRec[FieldType[K, V] :: T] = new ToMapRec[FieldType[K, V] :: T] {
def apply(l: FieldType[K, V] :: T): Map[String, Any] =
tmrT(l.tail) + (wit.value.name -> l.head)
}
}
object ToMapRec extends LowPriorityToMapRec {
implicit val hnilToMapRec: ToMapRec[HNil] = new ToMapRec[HNil] {
def apply(l: HNil): Map[String, Any] = Map.empty
}
implicit def hconsToMapRec0[K <: Symbol, V, R <: HList, T <: HList](implicit
wit: Witness.Aux[K],
gen: LabelledGeneric.Aux[V, R],
tmrH: ToMapRec[R],
tmrT: ToMapRec[T]
): ToMapRec[FieldType[K, V] :: T] = new ToMapRec[FieldType[K, V] :: T] {
def apply(l: FieldType[K, V] :: T): Map[String, Any] =
tmrT(l.tail) + (wit.value.name -> tmrH(gen.to(l.head)))
}
}
Lastly we provide some syntax for convenience:
implicit class ToMapRecOps[A](val a: A) extends AnyVal {
def toMapRec[L <: HList](implicit
gen: LabelledGeneric.Aux[A, L],
tmr: ToMapRec[L]
): Map[String, Any] = tmr(gen.to(a))
}
And then we can demonstrate that it works:
scala> p.toMapRec
res0: Map[String,Any] = Map(address -> Map(zip -> 10000, street -> Jefferson st), name -> Tom)
Note that this won't work for types where the nested case classes are in a list, tuple, etc., but you could extend it to those cases pretty straightforwardly.
I have a problem with an approach provided by Travis Brown.
Some of nesting case classes are not converted to Map https://scalafiddle.io/sf/cia2jTa/0.
The answer was found here.
To correct the solution just wrap ToMapRec[T] in implicit parameters to Lazy[ToMapRec[T]]. Corrected fiddle https://scalafiddle.io/sf/cia2jTa/1
The following code, which is taken from Apocalisp's excellent blog series:
Type level programming in scala , and modified for an implicit parsing scenario. However, this does not compile, with the following message:
error: ambiguous implicit values:
both method hParseNil in object HApplyOps of type => (com.mystuff.bigdata.commons.collections.hlist.HNil) => com.mystuff.bigdata.commons.collections.hlist.HNil
and method conforms in object Predef of type [A]<:<[A,A]
match expected type (com.mystuff.bigdata.commons.collections.hlist.HNil) => com.amadesa.bigdata.commons.collections.hlist.HNil
val l = hparse[HNil,HNil](HNil)
Can someone please explain why this happens, and if it's fixable?
sealed trait HList
final case class HCons[H, T <: HList](head: H, tail: T) extends HList {
def :+:[T](v: T) = HCons(v, this)
}
sealed class HNil extends HList {
def :+:[T](v: T) = HCons(v, this)
}
object HNil extends HNil
// aliases for building HList types and for pattern matching
object HList {
type :+:[H, T <: HList] = HCons[H, T]
val :+: = HCons
}
object HApplyOps
{
import HList.:+:
implicit def hParseNil: HNil => HNil = _ => HNil
implicit def hParseCons[InH,OutH,TIn <:HList,TOut<:HList](implicit parse:InH=>OutH,parseTail:TIn=>TOut): (InH :+: TIn) => (OutH :+: TOut) =
in => HCons(parse(in.head),parseTail(in.tail))
def hparse[In <: HList, Out <: HList](in:In)(implicit parse: In => Out):Out = in
}
object PG {
import HList._
def main(args: Array[String]) {
import HApplyOps._
val l = hparse[HNil,HNil](HNil)
}
}
Although i can't tell you exactly the purpose of Predef.conforms, i can tell you the ambiguity error seems correct (unfortunately). In the comment in the source it even says that <:< was introduced because of ambiguity problems of Function1 (says Function2 but i guess that is a mistake). But since <:< is a subclass of Function1 it can be passed in whenever Function1 is expected, so in your case its possible to pass in <:< to hparse.
Now the implicit def conforms[A]: A <:< A has the effect (from what i understand) that whenever a method expects a type A => A, it is sufficient to have an implicit value of A in scope.
In your case the implicit def hParseNil: HNil => HNil has the same priority as conforms and thus both could be equally applied.
I see two possible solutions:
just remove the hParseNil, i think your code still works.
shadow the Predef's conforms by naming yours the same:
implicit def conforms: HNil => HNil = _ => new HNil
You can just replace the function literal hParseNil to a normal function.
implicit def hParseNil(a:HNil): HNil = HNil
instead of
implicit def hParseNil: HNil => HNil = _ => HNil