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. S
The ShowBox
example you gave involves an existential type. I'm renaming the ShowBox
data constructor to SB
to distinguish it from the type:
data ShowBox = forall s. Show s => SB s
We say s
is "existential", but the forall
here is a universal quantifier that pertains to the SB
data constructor. If we ask for the type of the SB
constructor with explicit forall
turned on, this becomes much clearer:
SB :: forall s. Show s => s -> ShowBox
That is, a ShowBox
is actually constructed from three things:
s
s
Show s
.Because the type s
becomes part of the constructed ShowBox
, it is existentially quantified. If Haskell supported a syntax for existential quantification, we could write ShowBox
as a type alias:
type ShowBox = exists s. Show s => s
Scala does support this kind of existential quantification and Miles's answer gives the details using a trait that consists of exactly those three things above. But since this is a question about "forall in Scala", let's do it exactly like Haskell does.
Data constructors in Scala cannot be explicitly quantified with forall. However, every method on a module can be. So you can effectively use type constructor polymorphism as universal quantification. Example:
trait Forall[F[_]] {
def apply[A]: F[A]
}
A Scala type Forall[F]
, given some F
, is then equivalent to a Haskell type forall a. F a
.
We can use this technique to add constraints to the type argument.
trait SuchThat[F[_], G[_]] {
def apply[A:G]: F[A]
}
A value of type F SuchThat G
is like a value of the Haskell type forall a. G a => F a
. The instance of G[A]
is implicitly looked up by Scala if it exists.
Now, we can use this to encode your 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))
The ShowBox.apply
method is the universally quantified data constructor. You can see that it takes a type S
, an instance of Show[S]
, and a value of type S
, just like the Haskell version.
Here's an example usage:
scala> heteroList map { case ShowBox(x) => x }
res6: List[String] = List((), 5, true)
A more direct encoding in Scala might be to use a case class:
sealed trait ShowBox
case class SB[S:Show](s: S) extends ShowBox {
override def toString = Show[S].shows(s)
}
Then:
scala> val heteroList = List(ShowBox(()), ShowBox(5), ShowBox(true))
heteroList: List[ShowBox] = List((), 5, true)
In this case, a List[ShowBox]
is basically equivalent to a List[String]
, but you can use this technique with traits other than Show
to get something more interesting.
This is all using the Show
typeclass from Scalaz.