如何定义一个方法,该方法采用属于某个类型类的类型的异构对象序列?

时间:2014-08-07 15:34:42

标签: scala types typeclass shapeless

有一种类型可以通过某种有限的类型进行参数化:

trait Base[T] { def f(t: T): List[T] }
implicit object StringBase extends Base[String] {
  override def f(t: String) = t.toList.map(c => String.valueOf(c))
}
implicit object IntBase extends Base[Int] {
  override def f(t: Int) = List(t,t,t)
}

现在,我可以定义一个获取特定类型集合并处理它的函数:

def consume[T : Base](xs: Seq[T]) =
  xs.map(x => implicitly[Base[T]].f(x).mkString("-"))

如何定义一个函数,该函数为存在隐式转换为Base的类型获取一系列对象并执行相同的操作?当然,这是一种类型安全的方式。

如果我不完全清楚,这就是我想要的:

consume(Seq(1,"asd", 3)) // => Seq("1-1-1", "a-s-d", "3-3-3")

我确信我可以用无形的方式实现它。 HList,但核心Scala呢?无论如何,把shapeless标签放在一个有功能倾向的家伙的帮助下。

2 个答案:

答案 0 :(得分:1)

@RenderSection("Scripts", required: false)

Two key parts:

  1. import shapeless._ import ops.hlist.Mapper import ops.hlist.ToList trait Base[T] { def f(t: T): List[T] } implicit object StringBase extends Base[String] { override def f(t: String) = t.toList.map(c => String.valueOf(c)) } implicit object IntBase extends Base[Int] { override def f(t: Int) = List(t,t,t) } object base extends Poly1 { implicit def forBase[A : Base] = at[A](x => implicitly[Base[A]].f(x)) } def consume[T <: HList, Inter <: HList](xs: T) (implicit mapBase: Mapper.Aux[base.type, T, Inter], interToList: ToList[Inter, List[Any]]): Seq[String] = { xs.map(base).toList.map(_.mkString("-")) } : This is a polymorphic function only defined on types with a object base extends Poly1 typeclass instance. It calls Base on its arg.
  2. The implicits and type params in Base.f :
    • consume is our input HList type. T is an intermediate type variable that is the output type of the result of mapping Inter over base.
    • T : This is evidence that if we map mapBase: Mapper.Aux[base.type, T, Inter] over base we get T. We need this to call Inter on the incoming hlist. By putting it in our implicits list, we just require that it exists when the function is called. Shapeless will generate this for us if it can. If it can't, it most likely means that you forgot to define a .map instance for a type in Base
    • T : This is evidence that you can convert the HList interToList: ToList[Inter, List[Any]] to a Inter. So List[List[Any]] must be the least-upperbound type of all the types in List[Any]. Once again, by putting it in the input implicits, we just require that shapeless can generate this for us. This is just boilerplate to prove to the compiler that we can call Inter and get a toList. I haven't found a way to avoid this and the List[List[Any]] variable in my experiences sadly.

Now thanks to all those implicits, we just call Inter to get an HList out with the appropriate xs.map(base) applied. Then we call Base.f to get a .toList. Then we map over those List[List[Any]]s and List[Any] with dashes as you wanted. And out comes our mkString!

Here is it working in the REPL:

Seq[String]

答案 1 :(得分:0)

这是消费方法的脏黑客。不是超级类型安全和漂亮,但可能是一个很好的解决方法。

  trait Base[T] { def f(t: T): List[T] }
  implicit object StringBase extends Base[String] {
    override def f(t: String) = t.toList.map(c => String.valueOf(c))
  }
  implicit object IntBase extends Base[Int] {
    override def f(t: Int) = List(t,t,t)
  }

  case class Consumable[T](v: T, base: Base[T])

  implicit def toConsumable[T](v: T)(implicit base: Base[T], ev: ClassTag[T]) =
    Consumable(v, base)

  def consume(xs: Consumable[_]*) =
    xs.map(x => x.base.asInstanceOf[Base[Any]].f(x.v).mkString("-"))

  println(consume(1, 2, 3))
  println(consume("a", "b", "c"))
  println(consume(1, 2, "a", "b", "c"))