如何从函数中返回HList的多个变体?

时间:2013-08-13 19:39:54

标签: scala shapeless

具有

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`?”中描述的原因类似。

2 个答案:

答案 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

欢迎任何更好的解决方案。