[英]forall in 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"]
@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中的問題,但現在我們不能將它與我們存在量化List上的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參數,並用抽象val替換構造函數參數,
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中的忠實再現一樣接近。
您提供的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
forall a. F a
。
我們可以使用這種技術為類型參數添加約束。
trait SuchThat[F[_], G[_]] {
def apply[A:G]: F[A]
}
F SuchThat G
類型的值F SuchThat G
類似於Haskell類型的值forall a. G a => F a
forall a. G a => F a
。 如果Scala存在,則由Scala隱式查找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.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
類型類。
我不認為在這里可以從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)
( 編輯 :添加要顯示的方法,以回答評論。)
我認為你可以使用帶有上下文邊界的隱式方法來獲得相同的結果:
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)
}
}
正如當局的回答所暗示的那樣,我常常驚訝於Scala可以將“Haskell型怪物”翻譯成非常簡單的怪物。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.