Scala 中的 forall [英] forall in Scala

查看:26
本文介绍了Scala 中的 forall的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

As shown below, in Haskell, it's possible to store in a list values with heterogeneous types with certain context bounds on them:

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

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

如何在 Scala 中实现相同的效果,最好不要使用子类型?

How can I achieve the same in Scala, preferably without subtyping?

推荐答案

正如@Michael Kohl 所评论的,在 Haskell 中 forall 的这种使用是一种存在类型,并且可以使用 forSome 构造或通配符在 Scala 中完全复制.这意味着@paradigmatic 的回答在很大程度上是正确的.

As @Michael Kohl commented, this use of forall in Haskell is an existential type and can be exactly replicted in Scala using either the forSome construct or a wildcard. That means that @paradigmatic's answer is largely correct.

尽管如此,相对于 Haskell 原始版本还是缺少一些东西,即它的 ShowBox 类型的实例也捕获了相应的 Show 类型类实例,这使得它们可用于列表元素,即使确切的基础类型已经存在量化.您对@paradigmatic 的回答的评论表明您希望能够编写与以下 Haskell 等效的内容,

Nevertheless there's something missing there relative to the Haskell original which is that instances of its ShowBox type also capture the corresponding Show type class instances in a way which makes them available for use on the list elements even when the exact underlying type has been existentially quantified out. Your comment on @paradigmatic's answer suggests that you want to be able to write something equivalent to the following 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 基于类型类的方法.来了……

@Kim Stebel's answer shows the canonical way of doing that in an object-oriented language by exploiting subtyping. Other things being equal, that's the right way to go in Scala. I'm sure you know that, and have good reasons for wanting to avoid subtyping and replicate Haskell's type class based approach in Scala. Here goes ...

注意,在上面的 Haskell 中,Unit、Int 和 Bool 的 Show 类型类实例在 useShowBox 函数的实现中可用.如果我们尝试将其直接翻译成 Scala,我们会得到类似的结果,

Note that in the Haskell above the Show type class instances for Unit, Int and Bool are available in the implementation of the useShowBox function. If we attempt to directly translate this into Scala we'll get something like,

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 中编译失败,如下所示,

and this fails to compile in useShowBox as follows,

<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 函数上添加额外的上下文绑定来解决这个问题,

The problem here is that, unlike in the Haskell case, the Show type class instances aren't propagated from the ShowBox argument to the body of the useShowBox function, and hence aren't available for use. If we try to fix that by adding an additional context bound on the useShowBox function,

def useShowBox[T : Show](sb : ShowBox[T]) = sb match {
  case ShowBox(t) => implicitly[Show[T]].show(t) // Now compiles ...
} 

这解决了 useShowBox 中的问题,但现在我们不能将它与存在量化列表中的 map 结合使用,

this fixes the problem within useShowBox, but now we can't use it in conjunction with map on our existentially quantified List,

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 实例,那么就会有,但这不是我们想要的在这里之后……我们要根据每个列表元素的最具体的类型来选择一个类型类实例).

This is because when useShowBox is supplied as an argument to the map function we have to choose a Show instance based on the type information we have at that point. Clearly there isn't just one Show instance which will do the job for all of the elements of this list and so this fails to compile (if we had defined a Show instance for Any then there would be, but that's not what we're after here ... we want to select a type class instance based on the most specific type of each list element).

为了让它像在 Haskell 中一样工作,我们必须在 useShowBox 的主体中显式地传播 Show 实例.可能是这样的,

To get this to work in the same way that it does in Haskell, we have to explicitly propagate the Show instances within the body of useShowBox. That might go like this,

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 中,

then in the REPL,

scala> heteroList map useShowBox
res7: List[String] = List((), 5, true)

请注意,我们已经对 ShowBox 上的上下文绑定进行了脱糖,以便我们为包含的值的 Show 实例提供一个显式名称 (showInst).然后在 useShowBox 的主体中我们可以明确地应用它.还要注意,模式匹配对于确保我们在函数体中只打开一次存在类型至关重要.

Note that we've desugared the context bound on ShowBox so that we have an explicit name (showInst) for the Show instance for the contained value. Then in the body of useShowBox we can explicitly apply it. Also note that the pattern match is essential to ensure that we only open the existential type once in the body of the function.

很明显,这比等效的 Haskell 更加冗长,我强烈建议在 Scala 中使用基于子类型的解决方案,除非您有非常好的理由不这样做.

As should be obvious, this is a lot more vebose than the equivalent Haskell, and I would strongly recommend using the subtype based solution in Scala unless you have extremely good reasons for doing otherwise.

编辑

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

As pointed out in the comments, the Scala definition of ShowBox above has a visible type parameter which isn't present in the Haskell original. I think it's actually quite instructive to see how we can rectify that using abstract types.

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

First we replace the type parameter with an abstract type member and replace the constructor parameters with abstract vals,

trait ShowBox {
  type T
  val t : T
  val showInst : Show[T]
}

我们现在需要添加工厂方法,否则案例类将免费提供给我们,

We now need to add the factory method that case classes would otherwise give us for free,

object ShowBox {
  def apply[T0 : Show](t0 : T0) = new ShowBox {
    type T = T0
    val t = t0
    val showInst = implicitly[Show[T]]
  } 
}

我们现在可以在之前使用 ShowBox[_] 的任何地方使用普通的 ShowBox ......抽象类型成员现在为我们扮演存在量词的角色,

We can now use plain ShowBox whereever we previously used ShowBox[_] ... the abstract type member is playing the role of the existential quantifier for us now,

val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))

def useShowBox(sb : ShowBox) = {
  import sb._
  showInst.show(t)
}

heteroList map useShowBox

(值得注意的是,在 Scala 中引入显式 forSome 和通配符之前,这正是表示存在类型的方式.)

(It's worth noting that prior to the introduction of explict forSome and wildcards in Scala this was exactly how you would represent existential types.)

我们现在在与原始 Haskell 中完全相同的位置拥有存在.我认为这与您在 Scala 中所能获得的最接近的忠实再现一样.

We now have the existential in exactly the same place as it is in the original Haskell. I think this is as close to a faithful rendition as you can get in Scala.

这篇关于Scala 中的 forall的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆