全部在斯卡拉 [英] forall in Scala

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

问题描述

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

 数据ShowBox = forall s。 Show s => ShowBox s 

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

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

解决方案

正如@Michael Kohl评论的那样,Haskell中这种使用是存在类型,可以使用forSome构造或通配符在Scala中完全复制。这意味着@范型的答案在很大程度上是正确的。



尽管如此,Haskell的原始版本还是有一些缺失,即它的ShowBox类型的实例也捕获相应的Show类型实例的方式使得它们可以在列表元素上使用,即使精确的基础类型已经被存在地量化了。您对@ paradigmatic的回答的评论表明您希望能够编写相当于以下Haskell的内容:

 数据ShowBox = forall秒。 Show s => ShowBox s 

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

useShowBox :: ShowBox - > String
useShowBox(ShowBox s)= show s

- 然后在ghci ...

* Main> map useShowBox heteroList
[(),5,True]

@Kim Stebel的回答显示了通过利用子类型在面向对象语言中执行该操作的规范方式。其他事情是平等的,这是在斯卡拉正确的方式。我相信你知道这一点,并有充分的理由希望避免分类,并在Scala中复制Haskell基于类的类方法。这里有...



请注意,在Haskell上面的Unit类的Show类实例中,Int和Bool在useShowBox函数的实现中可用。如果我们试图直接将其转换为Scala,我们会得到类似于

  trait Show [T] {def show(t :T):String} 

//显示Unit
隐式对象的实例ShowUnit extends Show [Unit] {
def show(u:Unit):String = u.toString
}

//显示Int
隐式对象的实例ShowInt extends Show [Int] {
def show(i:Int):String = i.toString


//显示布尔实例
隐式对象ShowBool​​ean extends显示布尔值{
def show(b:Boolean):String = b.toString

$ b $ case case ShowBox [T:Show](t:T)
$ b $ def useShowBox [T](sb:ShowBox [T])= sb match {
case ShowBox(t)=>隐式地[Show [T]]。show(t)
//此处出错^^^^^^^^^^^^^^^^^^^
}

val杂项列表:列表[ShowBox [_]] =列表(ShowBox(()),ShowBox(5),ShowBox(true))

异步列表映射useShowBox

,并且在useShowBox中编译失败,如下所示:

 < console>:14:error:could not find implicit value for parameter e:Show [T] 
case ShowBox(t)=>隐式地[Show [T]]。show(t)
^

与Haskell情况不同,Show类类实例不会从ShowBox参数传播到useShowBox函数的主体,因此无法使用。如果我们试图通过在useShowBox函数中添加一个额外的上下文来解决这个问题,那么使用ShowShowBox [T:Show](sb) :ShowBox [T])= sb match {
case ShowBox(t)=>隐式地[Show [T]]。show(t)//现在编译...
}

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

 阶>杂项列表map useShowBox 
< console>:21:错误:找不到隐式值的证据参数
类型为Show [T]
异步映射useShowBox
^

这是因为当useShowBox作为参数提供给map函数时,我们必须选择一个基于类型的Show实例我们在这一点上的信息。很显然,不仅仅有一个Show实例会为列表中的所有元素执行这项工作,因此无法编译(如果我们已经为Any定义了一个Show实例,那么会出现这种情况,但那不是我们所知在这里之后......我们想要根据每个列表元素的最具体的类型来选择一个类型类实例)。

为了使它以相同的方式工作它在Haskell中,我们必须在useShowBox的主体中显式传播Show实例。这可能是这样的,

  case class ShowBox [T](t:T)(implicit val showInst:Show [T] )
$ b $ val杂项List:List [ShowBox [_]] = List(ShowBox(()),ShowBox(5),ShowBox(true))
$ b $ def useShowBox(sb :ShowBox [_])= sb match {
case sb @ ShowBox(t)=> sb.showInst.show(t)
}

然后在REPL中,

  scala>杂项列表map useShowBox 
res7:List [String] = List((),5,true)



<请注意,我们解除了ShowBox上的上下文绑定,以便为包含的值显示实例的名称(showInst)。然后在useShowBox的主体中,我们可以明确地应用它。还要注意模式匹配对于确保我们只在函数体内打开存在类型一次是必不可少的。



很明显,这是更多vebose比相当于Haskell,我强烈建议在Scala中使用基于子类型的解决方案,除非您有非常好的理由去做其他事情。



编辑



正如在注释中指出的那样,上面的ShowBox的Scala定义有一个可见的类型参数,它在Haskell原始中不存在。我认为这实际上是非常有益的,看看我们如何使用抽象类型来纠正它。

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

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

现在我们需要添加工厂方法,

  object ShowBox {
def apply [T0:Show](t0:T0)=新的ShowBox {
type T = T0
val t = t0
val showInst =隐式[显示[T]]
}
}

现在我们可以使用普通的ShowBox,无论我们以前使用ShowBox [_] ...抽象类型成员扮演着存在的角色我们现在的量词,

  val杂列表:List [ShowBox] = List(ShowBox(()),ShowBox(5), ShowBox(true))

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

ListList useShowBox

(值得注意的是,在Scala中引入一些通配符和通配符之前,这正是您如何表示存在类型的原因。)



现在我们在原有的Haskell中拥有完全相同的地方。我认为这与您可以在Scala中获得忠实的演绎非常接近。


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]

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

解决方案

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.

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'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 ...

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

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

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

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
                             ^

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

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

then in the REPL,

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

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.

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.

Edit

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

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

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

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.

这篇关于全部在斯卡拉的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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