【问题标题】:How to define a method that takes a heterogeneous sequence of objects of types belonging to a certain typeclass?如何定义一个方法,该方法采用属于某个类型类的类型对象的异构序列?
【发布时间】:2014-09-30 22:23:43
【问题描述】:

有一种类型可以通过特定的一组受限类型进行参数化:

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

我确信我可以通过 shapeless'HList 实现它,但是核心 Scala 呢?无论如何,把shapeless标签放在功能倾向的人愿意提供帮助的情况下。

【问题讨论】:

  • 问题在于推断的序列类型将是元素类型的最小上限(在本例中为Any)。您可以使用HList 编写它,或者您可以使用运行时反射来做一些糟糕的事情,但是如果您想要核心 Scala 类型安全,那么不幸的是您不走运。
  • @TravisBrown,这就是我害怕的。我知道这是对我的问题的最终答案,所以也许你应该在下面发布,以便我可以将问题标记为已关闭。

标签: scala types typeclass shapeless


【解决方案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("-"))
}

两个关键部分:

  1. object base extends Poly1 :这是一个多态函数,仅在具有 Base 类型类实例的类型上定义。它在其 arg 上调用 Base.f
  2. consume 中的隐式和类型参数:
    • T 是我们的输入 HList 类型。 Inter 是一个中间类型变量,它是baseT 的映射结果的输出类型。
    • mapBase: Mapper.Aux[base.type, T, Inter] :这证明如果我们将base 映射到T 我们得到Inter。我们需要这个来调用传入 hlist 上的.map。通过将它放在我们的隐含列表中,我们只要求它在调用函数时存在。如果可以的话,Shapeless 会为我们生成这个。如果不能,很可能意味着您忘记为T 中的类型定义Base 实例
    • interToList: ToList[Inter, List[Any]] :这是您可以将 HList Inter 转换为 List[List[Any]] 的证据。所以List[Any] 必须是Inter 中所有类型中上界最小的类型。再一次,通过将它放在输入隐式中,我们只需要 shapeless 可以为我们生成它。这只是向编译器证明我们可以调用toList 并获得List[List[Any]] 的样板。遗憾的是,在我的经历中,我还没有找到避免这种情况和Inter 变量的方法。

现在感谢所有这些隐式,我们只需调用xs.map(base) 来获取一个应用了适当Base.f 的HList。然后我们调用.toList 来获取List[List[Any]]。然后我们根据需要用破折号映射那些List[Any]s 和mkString。我们的Seq[String]出来了!

这是在 REPL 中工作的:

scala> consume(1 :: "asd" :: 3 :: HNil)
res0: Seq[String] = List(1-1-1, a-s-d, 3-3-3)

【讨论】:

    【解决方案2】:

    这是消耗方法的肮脏技巧。不是超级类型安全且美观,但作为一种解决方法可能很好。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-02
      相关资源
      最近更新 更多