如下所示,在Haskell中,可以在列表中存储具有不同类型的值,并对它们施加某些上下文限制:
data ShowBox = forall s. Show s => ShowBox s
heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]
我该如何在Scala中实现相同的功能,最好不使用子类型?
如下所示,在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"]
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)
^
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
^
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)
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]]
}
}
val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))
def useShowBox(sb : ShowBox) = {
import sb._
showInst.show(t)
}
heteroList map useShowBox
ShowBox
没有接受类型参数。存在类型(existential type)的整个意义就是隐藏这个类型参数。 - ApocalispBox [Show]
(或更一般地说,Box [TC [_]]
)代替ShowBox
?我的尝试可以在http://stackoverflow.com/a/28142861/247623中看到,但我不确定这是最好的方式。 - Erik Kaplun你提供的 ShowBox
示例涉及到一种 存在类型。我将 ShowBox
数据构造器重命名为 SB
,以便与该 类型 区分:
data ShowBox = forall s. Show s => SB s
我们说s
是“存在的”,但这里的forall
是一个普遍量词,它适用于SB
数据构造函数。如果我们要求打开显式forall
时SB
构造函数的类型,这一点会变得更加清晰:
SB :: forall s. Show s => s -> ShowBox
换句话说,一个ShowBox
实际上是由三部分构成的:
s
s
的值Show s
的一个实例。因为类型s
成为构造的ShowBox
的一部分,所以它是存在量化的。如果Haskell支持存在量化的语法,我们可以将ShowBox
写为类型别名:
type ShowBox = exists s. Show s => s
Scala支持这种存在量词,Miles的回答介绍了使用恰好包含上述三个内容的特质。但由于这是关于“Scala中的forall”的问题,让我们像Haskell一样来做。
在Scala中,数据构造函数不能显式地用forall进行量化。但是,模块上的每个方法都可以。因此,您可以有效地使用类型构造函数多态作为普遍量化。例如:
trait Forall[F[_]] {
def apply[A]: F[A]
}
对于给定的 F
,Scala 类型 Forall[F]
相当于 Haskell 类型 forall a. F a
。
我们可以使用这种技术来为类型参数添加约束。
trait SuchThat[F[_], G[_]] {
def apply[A:G]: F[A]
}
F SuchThat G
类型的值类似于 Haskell 类型 forall a. G a => F a
的值。如果实例 G[A]
存在,则 Scala 会隐式地查找它。
现在,我们可以使用这个来编码你的 ShowBox
...
import scalaz._; import Scalaz._ // to get the Show typeclass and instances
type ShowUnbox[A] = ({type f[S] = S => A})#f SuchThat Show
sealed trait ShowBox {
def apply[B](f: ShowUnbox[B]): B
}
object ShowBox {
def apply[S: Show](s: => S): ShowBox = new ShowBox {
def apply[B](f: ShowUnbox[B]) = f[S].apply(s)
}
def unapply(b: ShowBox): Option[String] =
b(new ShowUnbox[Option[String]] {
def apply[S:Show] = s => some(s.shows)
})
}
val heteroList: List[ShowBox] = List(ShowBox(()), ShowBox(5), ShowBox(true))
ShowBox.apply
方法是普适量的数据构造函数。你可以看到它接受一个类型为S
,一个Show[S]
实例和一个类型为S
的值,就像Haskell版本一样。
这里是一个使用示例:
scala> heteroList map { case ShowBox(x) => x }
res6: List[String] = List((), 5, true)
在Scala中更直接的编码方式可能是使用case class:
sealed trait ShowBox
case class SB[S:Show](s: S) extends ShowBox {
override def toString = Show[S].shows(s)
}
那么:
scala> val heteroList = List(ShowBox(()), ShowBox(5), ShowBox(true))
heteroList: List[ShowBox] = List((), 5, true)
在这种情况下,List[ShowBox]
基本上相当于 List[String]
,但您可以使用与 Show
不同的 trait 来获得更有趣的东西。 这里所使用的是来自 Scalaz 的 Show
typeclass。scala> trait Showable { def show:String }
defined trait Showable
scala> implicit def showableInt(i:Int) = new Showable{ def show = i.toString }
showableInt: (i: Int)java.lang.Object with Showable
scala> val l:List[Showable] = 1::Nil
l: List[Showable] = List($anon$1@179c0a7)
scala> l.map(_.show)
res0: List[String] = List(1)
trait Show[T] {
def apply(t:T): String
}
implicit object ShowInt extends Show[Int] {
def apply(t:Int) = "Int("+t+")"
}
implicit object ShowBoolean extends Show[Boolean] {
def apply(t:Boolean) = "Boolean("+t+")"
}
case class ShowBox[T: Show](t:T) {
def show = implicitly[Show[T]].apply(t)
}
implicit def box[T: Show]( t: T ) =
new ShowBox(t)
val lst: List[ShowBox[_]] = List( 2, true )
println( lst ) // => List(ShowBox(2), ShowBox(true))
val lst2 = lst.map( _.show )
println( lst2 ) // => List(Int(2), Boolean(true))
为什么不呢:
trait ShowBox {
def show: String
}
object ShowBox {
def apply[s](x: s)(implicit i: Show[s]): ShowBox = new ShowBox {
override def show: String = i.show(x)
}
}