如下所示,在Haskell中,可以将具有特定上下文边界的异构类型的值存储在列表中:
data ShowBox = forall s. Show s => ShowBox s
heteroList :: [ShowBox]
heteroList = [ShowBox (), ShowBox 5, ShowBox True]我怎样才能在Scala中实现同样的功能,最好是不需要子类型?
发布于 2011-08-27 20:08:55
正如@Michael Kohl评论的那样,在Haskell中使用forall是一种存在类型,并且可以使用forSome构造或通配符在Scala中精确复制。这意味着@paradigmatic的答案在很大程度上是正确的。
然而,与Haskell的原始版本相比,这里缺少了一些东西,即它的ShowBox类型的实例也捕获了相应的Show类型类实例,使得它们可以在list元素上使用,即使确切的底层类型已经被量化出来了。你对@ equivalent的回答的评论表明,你希望能够编写与以下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中,在useShowBox函数的实现中提供了Unit、Int和Bool的Show type类实例。如果我们尝试将它直接转换成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 type类实例不会从ShowBox参数传播到useShowBox函数体,因此不可用。如果我们试图通过在useShowBox函数上添加一个额外的上下文绑定来解决这个问题,
def useShowBox[T : Show](sb : ShowBox[T]) = sb match {
case ShowBox(t) => implicitly[Show[T]].show(t) // Now compiles ...
} 这修复了useShowBox中的问题,但现在我们不能将其与存在量化列表上的映射一起使用,
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实例可以完成这个列表中所有元素的工作,所以它无法编译(如果我们为任何元素定义了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中使用基于子类型的解决方案,除非你有非常好的理由。
编辑
正如评论中指出的,上面的ShowBox的Scala定义有一个可见的类型参数,这在Haskell的原始版本中是不存在的。我认为,看看我们如何使用抽象类型来纠正这一点,实际上是很有启发性的。
首先,我们用抽象类型成员替换类型参数,并用抽象类型替换构造函数参数,
trait ShowBox {
type T
val t : T
val showInst : Show[T]
}我们现在需要添加case类免费提供的工厂方法,
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(值得注意的是,在引入显式forSome和通配符之前,这正是您表示存在类型的方式。)
我们现在将存在词放在与原始Haskell完全相同的位置。我认为这是你在Scala中所能得到的最接近真实的再现。
发布于 2011-08-28 03:36:27
您给出的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 => sScala确实支持这种存在量化,并且Miles的回答使用一个特征给出了细节,该特征恰好由上述三件事组成。但由于这是一个关于“在Scala中的所有”的问题,让我们完全按照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类型的值类似于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类:
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以外的特征一起使用,以获得更有趣的东西。
这都是使用Scalaz的Show类型类实现的。
发布于 2011-08-27 18:36:14
我认为从Haskell到Scala的一对一转换在这里是不可能的。但是为什么你不想使用子类型呢?如果您想要使用的类型(如Int)缺少show方法,您仍然可以通过隐式转换添加此方法。
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)https://stackoverflow.com/questions/7213676
复制相似问题