Scala 中的 forall

2023-12-26

如下所示,在 Haskell 中,可以在列表中存储具有特定上下文边界的异构类型的值:

data ShowBox = forall s. Show s => ShowBox s

heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]

我如何在 Scala 中实现相同的目标,最好不使用子类型?


正如 @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 中的问题,但现在我们不能将它与我们的存在量化列表上的 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 中使用基于子类型的解决方案,除非您有充分的理由不这样做。

Edit

正如评论中所指出的,上面的 ShowBox 的 Scala 定义有一个可见的类型参数,而 Haskell 原始版本中不存在该参数。我认为了解如何使用抽象类型来纠正这个问题实际上非常有启发性。

首先我们用抽象类型成员替换类型参数,并用抽象值替换构造函数参数,

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 中引入显式 fourSome 和通配符之前,这正是表示存在类型的方式。)

现在,我们的存在主义位于与原始 Haskell 中完全相同的位置。我认为这是在 Scala 中最接近忠实的再现。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Scala 中的 forall 的相关文章

随机推荐