Scala generic Function vs Generic Method

Viewed 135

So far i was under the impression that the only way to define Generic function in scala, was using Method e.g.

def myToString[A](value: A) = {println(value)}

However i figured the following way:

val myToStringFun: (T forSome {type T}) => Unit = value => println(value)

Is there something that i am not seeing or is that the pattern to write generic function in Scala without resorting to method ?

I never seen that pattern before, and only came up with it, based my learning around Higher-Kinded Type and the notion of existential ...

Please share your thoughts or wisdom on this....

EDIT1:

If the above is correct, why is it that this pattern is not used and people systematically resort to Method for generic function. Is it just a ease of notation

EDIT2:

If a function (T forSome {type T}) => Unit <=> Any => Unit

It seems however that

val mySeqToString: (Seq[T] forSome {type T}) => String = {
    case head +: tail => s"$head +: " + seqToString(tail)
    case Nil => "Nil"
}

is equivalent to

def seqToString[T](seq: Seq[T]): String = seq match {
    case head +: tail => s"$head +: " + seqToString(tail)
    case Nil => "Nil"
}

correct ?

3 Answers

Type A in your function is a standard generic type. You can do many things with it, such as demand a typeclass instance:

import cats.implicits._
def myToString[A: cats.Show](value: A) = { println(value.show) }

On the other hand, type (T forSome {type T}) is an existential type. You might recognize it by its more popular shorthand notation, _ (e.g. List[_]). Not much you can do with that one. If you check the type of value in

val myToStringFun: (T forSome { type T }) => Unit = value => println(value)

you will notice that it's Any. BTW, using existential types via forSome is being dropped.

Functions are monomorphic in Scala, unlike methods that are polymorphic. Personally, I think this article(s) provides a great explanation on that topic.

(T forSome { type T }) is just Any, so (T forSome { type T }) => Unit is Any => Unit, which is a subtype of arbitrary A => Unit.

Generally F[T] forSome { type T } is not F[Any] (it's so for covariant F i.e. F[+X]). F[T] forSome {type T} aka F[_] is a supertype of all types F[A] (including F[Any]). Actually, it's the minimal such supertype i.e. the least upper bound of all types F[A] (for fixed F and arbitrary A).

It seems however that

val mySeqToString: (Seq[T] forSome {type T}) => String = {
  case head +: tail => s"$head +: " + seqToString(tail)
  case Nil => "Nil" 
}

is equivalent to

def seqToString[T](seq: Seq[T]): String = seq match {
  case head +: tail => s"$head +: " + seqToString(tail)
  case Nil => "Nil" 
}

correct ?

No.

def seqToString[T](seq: Seq[T]): String is universal quantification

seqToString: (∀ T) => (seq: Seq[T]) => String

while val mySeqToString: (Seq[T] forSome {type T}) => String is existential quantification

seqToString: ((∃ T), (seq: Seq[T])) => String

In the first case you can specify T and your code will work for this specific T, e.g. seqToString[Int] will accept Seq[Int], seqToString[String] will accept Seq[String] etc.

In the second case you do not control T, the method accepts all Seq[Int], Seq[String] etc.

Since Seq is covariant, Seq[T] forSome { type T } is just Seq[Any].

In a dependently typed language existential quantification leads to Sigma-types, universal quantification leads to Pi-types.

Scala 3 (Dotty) should provide polymorphic function types, so akin to polymorphic method

scala> def myToString[A](value: A) = println(value)
def myToString[A](value: A): Unit

we could write polymorphic function

scala> val myToString: [A] => A => Unit = [A] => (value: A) => println(value)
val myToString: PolyFunction{apply: [A](x$1: A): Unit} = <function1>

which is equivalent to PolyFunction refined with polymorphic apply method

scala> val myToString: PolyFunction {def apply[A](value: A): Unit} = new PolyFunction {
     |   def apply[A](value: A): Unit = println(value)
     | }
val myToString: PolyFunction{apply: [A](value: A): Unit} = <function1>

Mind to not confuse polymorphic function type

[A] => B

with type lambda which is on a different "level"

[A] =>> B

where the second > in arrow =>> lifts to a type-level.

Note this feature is still being worked on as per documentation is missing polymorphic functions #7594

Related