简体   繁体   中英

what are the use cases of existentials in Scala if there is subtyping?

Is there any meaningful reason why existentials exist in Scala if Scala has also subtyping?

For example in Haskell existentials can be used for heterogenous lists but in Scala heterogeneous lists can be created just by using subtyping.

This makes me wonder why would anyone ever want to use existentials if there is subtyping in Scala ? Is there any use case for existentials that cannot be more conviniently solved by subtyping ? I am not sure there is. Any counterexamples ?

EDIT: Existentials are useful for defining higher kinded types (Functor, Monad, etc), that I understand, but besides them, is there any other meaningful use case ?

Example of existentials from my working project:

Implementing type safe parameter storage

trait Parameter[T] // T is a type of a value related to this parameter

trait ParameterStorage {
   def getValue[T](p: Parameter[T]): Option[T]
}

//implementation
type ParamAndValue = (Parameter[T], T) forSome { type T; }

//initialize storage with pairs of parameters and values
class StorageImpl(pairs: ParamAndValue*) { 
   ...
}

Consider eg Array[_] . It isn't at all the same as Array[Any] , eg

val x: Array[Any] = new Array[Any](5)
x(0) = "" // legal
val y: Array[_] = new Array[Double](5)
y(0) = "" // illegal

EDIT: Existentials are useful for defining higher kinded types (Functor, Monad, etc), that I understand, but besides them, is there any other meaningful use case ?

No. F[_] in Functor[F[_]] looks the same as an existential, but isn't one at all.

Here is an example that came up just right now:

object Test{
  trait Entity
  case class SEntity() extends Entity
  case class RefVal[T<:Entity](a:T)
  def g(o:RefVal[Entity])= ???
  def g2(o:RefVal[_<:Entity])= ???
  g(RefVal[SEntity](a=SEntity())) // does not compile
  g2(RefVal[SEntity](a=SEntity())) // does compile

}

If I want to avoid making RefVal covariant then I need to use existentials here.

In other words, existentials are useful to set covariance in a more fine grained resolution, for example on the method level as shown above as opposed to globally setting RefVal covariant by declaring it as RefVal[+T<:Entity] .

EDIT2:

Here is some other useful usage of existentials:

object Existentials_List extends App {

  class A[T]

  class J

  class C1() extends J

  class C2() extends J

  class Z

  val l: Set[A[_ <: J]] = Set()

  val l2: Set[A[_ <: J]] = l + new A[J]   
  val l3: Set[A[_ <: J]] = l + new A[C1]   
  val l4: Set[A[_ <: J]] = l + new A[Z] // does not compile

}

Other possibility would have been to make class A[+T] and just do Set[A[J]] but that can make a lot of things messy, the covariant + annotation can infect large part of the code where non of that covariant stuff matters at all. So keeping things invariant and using existentials at one place (in the collection where the objects are stored), might be a simpler solution than littering the code with covariance annotations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM