The types of symbols class A[_]
or of def a[_](x: Any)
have a type parameter that can't be referenced in the body, thus I don't see where it is useful for and why it compiles. If one tries to reference this type parameter, an error is thrown:
scala> class A[_] { type X = _ }
<console>:1: error: unbound wildcard type
class A[_] { type X = _ }
^
scala> def a[_](x: Any) { type X = _ }
<console>:1: error: unbound wildcard type
def a[_](x: Any) { type X = _ }
^
Can someone tell me if such a type has a use case in Scala? To be exact, I do not mean existential types or higher kinded types in type parameters, only those litte [_]
which form the complete type parameter list.
Because I did not get the answers I expected, I brought this to scala-language.
I paste here the answer from Lars Hupel (so, all credits apply to him), which mostly explains what I wanted to know:
I'm going to give it a stab here. I think the use of the feature gets clear when talking about type members.
Assume that you have to implement the following trait:
trait Function { type Out[In] def apply[In](x: In): Out[In] }
This would be a (generic) function where the return type depends on the input type. One example for an instance:
val someify = new Function { type Out[In] = Option[In] def apply[In](x: In) = Some(x) } someify(3) res0: Some[Int] = Some(3)
So far, so good. Now, how would you define a constant function?
val const0 = new Function { type Out[In] = Int def apply[In](x: In) = 0 } const0(3) res1: const0.Out[Int] = 0
(The type
const0.Out[Int]
is equivalent toInt
, but it isn't printed that way.)Note how the type parameter
In
isn't actually used. So, here's how you could write it with_
:val const0 = new Function { type Out[_] = Int def apply[In](x: In) = 0 }
Think of
_
in that case as a name for the type parameter which cannot actually be referred to. It's a for a function on the type level which doesn't care about the parameter, just like on value level:(_: Int) => 3 res4: Int => Int = <function1>
Except …
type Foo[_, _] = Int <console>:7: error: _ is already defined as type _ type Foo[_, _] = Int
Compare that with:
(_: Int, _: String) => 3 res6: (Int, String) => Int = <function2>
So, in conclusion:
type F[_] = ConstType // when you have to implement a type member def foo[_](...) // when you have to implement a generic method but don't // actually refer to the type parameter (occurs very rarely)
The main thing you mentioned,
class A[_]
, is completely symmetric to that, except that there's no real use case.Consider this:
trait FlyingDog[F[_]] { def swoosh[A, B](f: A => B, a: F[A]): F[B] }
Now assume you want to make an instance of
FlyingDog
for your plain oldclass A
.new FlyingDog[A] { ... } // error: A takes no type parameters, expected: one // (aka 'kind mismatch')
There are two solutions:
Declare
class A[_]
instead. (Don't do that.)Use a type lambda:
new FlyingDog[({ type λ[α] = A })#λ]
or even
new FlyingDog[({ type λ[_] = A })#λ]
I had some casual ideas about what it could mean here:
https://issues.scala-lang.org/browse/SI-5606
Besides the trivial use case, asking the compiler to make up a name because I really don't care (though maybe I'll name it later when I implement the class), this one still strikes me as useful:
Another use case is where a type param is deprecated because improvements in type inference make it superfluous.
trait T[@deprecated("I'm free","2.11") _, B <: S[_]]
Then, hypothetically, one could warn on usage of
T[X, Y]
but notT[_, Y]
.
Though it's not obvious whether the annotation would come before (value parameter-style) or after (annotation on type style).
[Edit: "why it compiles": case class Foo[_](i: Int)
still crashes nicely on 2.9.2]
The underscore in Scala indicates an existential type, i.e. an unknown type parameter, which has two main usage:
A type constructor is basically something that needs a type parameter to construct a concrete type. For example you can take the following signature.
def strangeStuff[CC[_], B, A](b:B, f: B=>A): CC[A]
This is a function that for some CC[_]
, for example a List[_]
, creates a List[A]
starting from a B and a function B=>A
.
Why would that be useful? Well it turns out that if you use that mechanism together with implicits and typeclasses, you can get what is called ad-hoc polymorphism thanks to the compiler reasoning.
Imagine for example you have some higher-kinded type: Container[_]
with a hierarchy of concrete implementations: BeautifulContainer[_]
, BigContainer[_]
, SmallContainer[_]
. To build a container you need a
trait ContainerBuilder[A[_]<:Container[_],B] {
def build(b:B):A[B]
}
So basically a ContainerBuilder is something that for a specific type of container A[_] can build an A[B] using a B.
While would that be useful ? Well you can imagine that you might have a function defined somewhere else like the following:
def myMethod(b:B)(implicit containerBuilder:ContainerBuilder[A[_],B]):A[B] = containerBuilder.build(b)
And then in your code you might do:
val b = new B()
val bigContainer:BigContainer[B] = myMethod(b)
val beautifulContainer:BeautifulContainer[B] = myMethod(b)
In fact, the compiler will use the required return type of myMethod to look for an implicit which satisfies the required type constraints and will throw a compile error if there is no ContainerBuilder which meets the required constraints available implicitely.
That's useful when you deal with instances of parametrized types without caring of the type parameter.
trait Something[A] {
def stringify: String
}
class Foo extends Something[Bar] {
def stringify = "hop"
}
object App {
def useSomething(thing: Something[_]) :String = {
thing.stringify
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With