正如@Michael Kohl 评论的那样,在 Haskell 中使用 forall 是一种存在类型,可以使用 forSome 构造或通配符在 Scala 中完全复制。这意味着@paradigmatic 的答案在很大程度上是正确的。
尽管如此,相对于 Haskell 原始版本,还是缺少了一些东西,即它的 ShowBox 类型的实例也捕获了相应的 Show 类型类实例,即使在确切的基础类型已经存在时,它们也可以在列表元素上使用。存在量化的。您对@paradigmatic 的回答的评论表明您希望能够编写与以下 Haskell 等效的东西,
data ShowBox = forall s. Show s => ShowBox s
heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]
useShowBox :: ShowBox -> String
useShowBox (ShowBox s) = show s
-- Then in ghci ...
*Main> map useShowBox heteroList
["()","5","True"]
@Kim Stebel 的回答显示了通过利用子类型在面向对象语言中做到这一点的规范方式。在其他条件相同的情况下,这是 Scala 的正确方法。我相信您知道这一点,并且有充分的理由希望避免子类型化并在 Scala 中复制 Haskell 的基于类型类的方法。来了……
请注意,在上面的 Haskell 中,Unit、Int 和 Bool 的 Show 类型类实例在 useShowBox 函数的实现中可用。如果我们尝试将其直接翻译成 Scala,我们会得到类似的东西,
trait Show[T] { def show(t : T) : String }
// Show instance for Unit
implicit object ShowUnit extends Show[Unit] {
def show(u : Unit) : String = u.toString
}
// Show instance for Int
implicit object ShowInt extends Show[Int] {
def show(i : Int) : String = i.toString
}
// Show instance for Boolean
implicit object ShowBoolean extends Show[Boolean] {
def show(b : Boolean) : String = b.toString
}
case class ShowBox[T: Show](t:T)
def useShowBox[T](sb : ShowBox[T]) = sb match {
case ShowBox(t) => implicitly[Show[T]].show(t)
// error here ^^^^^^^^^^^^^^^^^^^
}
val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))
heteroList map useShowBox
这在useShowBox中编译失败,如下所示,
<console>:14: error: could not find implicit value for parameter e: Show[T]
case ShowBox(t) => implicitly[Show[T]].show(t)
^
这里的问题是,与 Haskell 的情况不同,Show 类型类实例不会从 ShowBox 参数传播到 useShowBox 函数的主体,因此无法使用。如果我们尝试通过在 useShowBox 函数上添加额外的上下文来解决这个问题,
def useShowBox[T : Show](sb : ShowBox[T]) = sb match {
case ShowBox(t) => implicitly[Show[T]].show(t) // Now compiles ...
}
这解决了 useShowBox 中的问题,但现在我们不能将它与我们存在量化的 List 上的 map 结合使用,
scala> heteroList map useShowBox
<console>:21: error: could not find implicit value for evidence parameter
of type Show[T]
heteroList map useShowBox
^
这是因为当 useShowBox 作为参数提供给 map 函数时,我们必须根据当时的类型信息选择一个 Show 实例。显然,不只有一个 Show 实例可以为该列表的所有元素完成这项工作,因此无法编译(如果我们为 Any 定义了 Show 实例,那么会有,但这不是我们想要的在这里之后...我们要根据每个列表元素的最具体的类型来选择一个类型类实例)。
要让它以与在 Haskell 中相同的方式工作,我们必须在 useShowBox 的主体内显式传播 Show 实例。可能会这样,
case class ShowBox[T](t:T)(implicit val showInst : Show[T])
val heteroList: List[ShowBox[_]] = List(ShowBox(()), ShowBox(5), ShowBox(true))
def useShowBox(sb : ShowBox[_]) = sb match {
case sb@ShowBox(t) => sb.showInst.show(t)
}
然后在 REPL 中,
scala> heteroList map useShowBox
res7: List[String] = List((), 5, true)
请注意,我们已经对 ShowBox 上绑定的上下文进行了脱糖处理,以便我们为包含的值的 Show 实例提供一个明确的名称 (showInst)。然后在 useShowBox 的主体中我们可以显式地应用它。另请注意,模式匹配对于确保我们只在函数体中打开存在类型一次是必不可少的。
显然,这比等效的 Haskell 更加麻烦,我强烈建议您在 Scala 中使用基于子类型的解决方案,除非您有充分的理由不这样做。
编辑
正如 cmets 中所指出的,上面 ShowBox 的 Scala 定义有一个可见的类型参数,这在 Haskell 原版中是不存在的。我认为看看我们如何使用抽象类型来纠正这一点实际上很有启发性。
首先我们将类型参数替换为抽象类型成员,并将构造函数参数替换为抽象 val,
trait ShowBox {
type T
val t : T
val showInst : Show[T]
}
我们现在需要添加工厂方法,否则案例类会免费提供给我们,
object ShowBox {
def apply[T0 : Show](t0 : T0) = new ShowBox {
type T = T0
val t = t0
val showInst = implicitly[Show[T]]
}
}
我们现在可以在之前使用 ShowBox[_] 的任何地方使用普通 ShowBox ...抽象类型成员现在为我们扮演存在量词的角色,
val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))
def useShowBox(sb : ShowBox) = {
import sb._
showInst.show(t)
}
heteroList map useShowBox
(值得注意的是,在 Scala 中引入显式 forSome 和通配符之前,这正是您表示存在类型的方式。)
我们现在在与原来的 Haskell 完全相同的地方拥有存在主义。我认为这是您在 Scala 中可以得到的最接近忠实的演绎。