斯卡拉的forall

时间:2011-08-27 09:31:36

标签: scala haskell forall

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

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

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

如何在Scala中实现相同的功能,最好不要进行子类型化?

5 个答案:

答案 0 :(得分:62)

正如@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类型实例中,Unit,Int和Bool在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中使用基于子类型的解决方案,除非你有充分的理由不这样做。

修改

正如评论中所指出的,上面ShowBox的Scala定义具有可见的类型参数,该参数在Haskell原始文件中不存在。我认为看看我们如何使用抽象类型来纠正它是非常有益的。

首先,我们用一个抽象类型成员替换type参数,并用abstract abstracts替换构造函数参数,

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和wildcards的explict之前,这正是你代表存在类型的方式。)

我们现在拥有与原始Haskell完全相同的存在体。我认为这与你在Scala中得到的忠实再现一样接近。

答案 1 :(得分:24)

您提供的ShowBox示例涉及存在类型。我正在将ShowBox数据构造函数重命名为SB,以区别于类型

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

我们说s是“存在主义”,但forall这里是一个与SB数据构造函数相关的通用量词。如果我们要求明确SB的{​​{1}}构造函数的类型打开,则会更加清晰:

forall

也就是说,SB :: forall s. Show s => s -> ShowBox 实际上是由三件事构成的:

  1. 类型ShowBox
  2. 类型s
  3. 的值
  4. s
  5. 的一个实例

    因为类型Show s成为构建的s的一部分,所以存在量化。如果Haskell支持存在量化的语法,我们可以将ShowBox写为类型别名:

    ShowBox

    Scala确实支持这种存在量化,而Miles的答案使用的特征恰好包含了上述三个特征。但由于这是一个关于“Scala中的forall”的问题,让我们完全像Haskell那样做。

    Scala中的数据构造函数无法使用forall进行显式量化。但是,模块上的每个方法都可以。因此,您可以有效地使用类型构造函数多态作为通用量化。例如:

    type ShowBox = exists s. Show s => s
    

    给定一些trait Forall[F[_]] { def apply[A]: F[A] } 的Scala类型Forall[F]等同于Haskell类型F

    我们可以使用这种技术为类型参数添加约束。

    forall a. F a

    类型trait SuchThat[F[_], G[_]] { def apply[A:G]: F[A] } 的值类似于Haskell类型F SuchThat G的值。如果存在,则Scala会隐式查找forall a. G a => F a的实例。

    现在,我们可以使用它来编码您的G[A] ...

    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.applyS的实例和类型Show[S]的值,就像Haskell版本一样。

    以下是一个示例用法:

    S

    Scala中更直接的编码可能是使用案例类:

    scala> heteroList map { case ShowBox(x) => x }
    res6: List[String] = List((), 5, true)
    

    然后:

    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]以外的特征结合使用,以获得更有趣的内容。

    这都是使用Scalaz中的Show类型类。

答案 2 :(得分:5)

我认为这里不可能从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)

答案 3 :(得分:3)

编辑:添加要显示的方法,以回答评论。)

我认为使用带有上下文边界的隐式方法可以获得相同的结果:

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))

答案 4 :(得分:0)

为什么不:

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)
    }
}

正如当局的答案所示, 我经常感到惊讶的是,Scala可以将“Haskell型怪物”翻译成非常简单的。