具有
class A
class B extends A
class C extends A
class Container[+L <: HList](l: L)
shapeless方式代码如下?:
def foo[L <: HList](a: A): Container[L] = a match {
case (b: B) => new Container(1 :: "a" :: HNil)
case (c: C) => new Container(1.0 :: HNil)
case _ => new Container(HNil)
}
然后以某种方式使用它:
val l1: Container[Int :: String :: HNil] = foo(new B)
val l2: Container[Double :: HNil] = foo(new C)
val l3: Container[String :: HNil] = foo(new C) // Compile-time error
请注意,上述方式主要是错误的,原因与“Why `List[B]` is not a subtype of `Seq[L]` when `class B extends A` and `L <: A`?”中描述的原因类似。
答案 0 :(得分:4)
你可以使用无形'多态函数:
// Base case
class LowPrioFoo extends Poly1 {
implicit def default[X] = at[X] { _ => new Container(HNil) }
}
// Specific cases
object foo extends LowPrioFoo {
implicit def atB = at[B] { _ => new Container(1 :: "a" :: HNil) }
implicit def atC = at[C] { _ => new Container(1.0 :: HNil) }
}
现在您可以根据需要调用函数foo
:
val x = foo(new A): Container[HNil]
val y = foo(new B): Container[Int :: String :: HNil]
val z = foo(new C): Container[Double :: HNil]
这与你的基本相同,但它由foo
封装(并且接口更好,无形)。这样您就可以确保不会发生意外转换。
<强>附录强>
正如@MilesSabin所指出的,如果不使用参数的值,那么对于无形的'多态函数没有多大用处。基于类型类的简单解决方案可能更好。这样的解决方案如下:
trait Foo[T] {
type R
def result: R
}
trait LowPrioFoo {
implicit def default[X] = new Foo[X] {
type R = Container[HNil]
val result = new Container(HNil)
}
}
object Foo extends LowPrioFoo {
implicit val bFoo = new Foo[B] {
type R = Container[Int :: String :: HNil]
val result = new Container(1 :: "a" :: HNil)
}
implicit val cFoo = new Foo[C] {
type R = Container[Double :: HNil]
val result = new Container(1.0 :: HNil)
}
}
def foo[A](x: A)(implicit f: Foo[A]): f.R = f.result
请注意,这已经非常接近Poly
的内部工作原理。将特征Foo
与特征CaseAux
and Poly#Case
进行比较,其中参数模型为HList
,并允许result
依赖于实际值。这使Foo
成为这些类型类的特例。
答案 1 :(得分:0)
终于明白了问题的本质......似乎如下:
implicit def fromA(a: A): Container[HNil] = new Container(HNil)
implicit def fromB(b: B): Container[Int :: String :: HNil] = new Container(1 :: "a" :: HNil)
implicit def fromC(c: C): Container[Double :: HNil] = new Container(1.0 :: HNil)
然后它有资格写:
val l1: Container[Int :: String :: HNil] = new B
val l2: Container[Double :: HNil] = new C
// val l3: Container[String :: HNil] = new C // Compile-time error
val l4: Container[HNil] = new A
欢迎任何更好的解决方案。