以下に示すように、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では、UseShowBox関数の実装で、Unit、Int、およびBoolのShow型クラスインスタンスを使用できることに注意してください。これを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内の問題が修正されますが、存在記号リストのマップと組み合わせて使用することはできなくなりました。
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インスタンスは1つだけではないため、コンパイルに失敗します(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)
含まれている値のShowインスタンスの明示的な名前(showInst)を持つように、ShowBoxにバインドされたコンテキストを脱糖化したことに注意してください。次に、useShowBoxの本体で、明示的に適用できます。また、関数の本体で存在型を1回だけ開くようにするには、パターンマッチングが不可欠であることに注意してください。
当然のことながら、これは同等の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
(Scalaにexplict forSomeとワイルドカードが導入される前は、これが実存的なタイプを表す方法であったことに注意してください。)
これで、元のHaskellとまったく同じ場所に実存があります。これは、Scalaで得られる限り忠実な表現に近いと思います。
Haskell から Scala への 1 対 1 の変換は、ここでは不可能だと思います。しかし、サブタイピングを使用したくないのはなぜですか? 使用したい型 (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 タイプのモンスター」を非常に単純なものに変換できることに、私はよく驚かされます。