The error in Test.test seems unjustified:
sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]
case class Test[U]() {
def test[V](t: A[Option[U], V]) = t match {
case B() => null // constructor cannot be instantiated to expected type; found : B[V] required: A[Option[U],?V1] where type ?V1 <: V (this is a GADT skolem)
}
def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}
object Test2 {
def test2[U, V](t: A[Option[U], V]) = t match {
case B() => null // This works
}
}
There are a couple ways to make the error change, or go away:
If we remove the V parameter on trait A (and case class B), the 'GADT-skolem' part of the error goes away but the 'constructor cannot be instantiated' part remains.
If we move the U parameter of the Test class to the Test.test method, the error goes away. Why ? (Similarly, the error is not present in Test2.test2)
The following link also identifies that problem, but I do not understand the provided explanation. http://lambdalog.seanseefried.com/tags/GADTs.html
Is this an error in the compiler ? (2.10.2-RC2)
Thank you for any help with clarifying that.
2014/08/05: I have managed to further simplify the code, and provide another example where U is bound outside the immediate function without causing a compilation error. I still observe this error in 2.11.2.
sealed trait A[U]
case class B() extends A[Unit]
case class Test[U]() {
def test(t: A[U]) = t match {
case B() => ??? // constructor cannot be instantiated to expected type; found : B required: A[U]
}
}
object Test2 {
def test2[U](t: A[U]) = t match {
case B() => ??? // This works
}
def test3[U] = {
def test(t: A[U]) = t match {
case B() => ??? // This works
}
}
}
Simplified like that this looks more like a compiler bug or limitation. Or am I missing something ?
Constructor patterns must conform to the expected type of the pattern, which means B <: A[U], a claim which is true if U is a type parameter of the method presently being called (because it can be instantiated to the appropriate type argument) but untrue if U is a previously bound class type parameter.
You can certainly question the value of the "must conform" rule. I know I have. We generally evade this error by upcasting the scrutinee until the constructor pattern conforms. Specifically,
// Instead of this
def test1(t: A[U]) = t match { case B() => ??? }
// Write this
def test2(t: A[U]) = (t: A[_]) match { case B() => ??? }
Addendum: in a comment the questioner says "The question is simple on why it doesn't works with type parameter at class level but works with at method level." Instantiated class type parameters are visible externally and have an indefinite lifetime, neither of which is true for instantiated method type parameters. This has implications for type soundness. Given Foo[A], once I'm in possession of a Foo[Int] then A must be Int when referring to that instance, always and forever.
In principle you could treat class type parameters similarly inside a constructor call, because the type parameter is still unbound and can be inferred opportunistically. That's it, though. Once it's out there in the world, there's no room to renegotiate.
One more addendum: I see people doing this a lot, taking as a premise that the compiler is a paragon of correctness and all that's left for us to do is ponder its output until our understanding has evolved far enough to match it. Such mental gymnastics have taken place under that tent, we could staff Cirque de Soleil a couple times over.
scala> case class Foo[A](f: A => A)
defined class Foo
scala> def fail(foo: Any, x: Any) = foo match { case Foo(f) => f(x) }
fail: (foo: Any, x: Any)Any
scala> fail(Foo[String](x => x), 5)
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at $anonfun$1.apply(<console>:15)
at .fail(<console>:13)
... 33 elided
That's the current version of scala - this is still what it does. No warnings. So maybe ask yourselves whether it's wise to be offering the presumption of correctness to a language and implementation which are so trivially unsound after more than ten years of existence.
Looks like it is a compiler caveat. From this, martin odersky puts it as:
In the method case, what you have here is a GADT:
Patterns determine the type parameters of an corresponding methods in the scope of a pattern case.
GADTs are not available for class parameters.
As far as I know, nobody has yet explored this combination, and it looks like it would be quite tricky to get this right.
PS: Thanks to #retronym who provided the reference from discussion here
On why it throws an error in case of class:
This works:
sealed trait A[-K, +V]
case class B[+V]() extends A[Option[Unit], V]
case class Test[U]() {
def test[V, X <: Unit](t: A[Option[X], V]) = t match {
case B() => null
}
def test2[V](t: A[Option[U], V]) = Test2.test2(t)
}
object Test2 {
def test2[U, V](t: A[Option[U], V]) = t match {
case B() => null // This works
}
}
To examplain why the compiler threw an error: Try doing this:
scala> :paste
// Entering paste mode (ctrl-D to finish)
abstract class Exp[A]{
def eval:A = this match {
case Temp(i) => i //line-a
}
}
case class Temp[A](i:A) extends Exp[A]
// Exiting paste mode, now interpreting.
To understand this, from §8.1.6: above Temp is a polymorphic type. If the case class is polymorphic then:
If the case class is polymorphic, then its type parameters are
instantiated so that the instantiation of c conforms to the expected
type of the pattern. The instantiated formal parameter types of c’s
primary constructor are then taken as the expected types of the
component patterns p 1 , . . . , p n . The pattern matches all objects
created from constructor invocations c(v1 , . . . , v n ) where each
element pattern p i matches the corresponding value v i .
i.e. the compiler smartly tries to instantiate Temp in line-a such that it conforms to the primary constructor of this( which if above compiled successfully then in case of Temp(1) would be something like Exp[Int] and hence compiler instantiates Temp in line-a with parameter as Int.
Now in our case: the compiler is trying to instantiate B. It sees that t is of type A[Option[U],V] where U is already fixed and obtained from class parameter and V is generic type of method. On trying to initialize B it tries to create in such a way that it ultimately gets A[Option[U],V]. So with B() it is somehow trying to get A[Option[U],V]. But it cant as B is A[Option[Unit],V]. Hence it ultimately cannot initialize B. Fixing this makes it work
Its not required in case of test-2 because: The compiler as explained in above process is trying to initialize type parameter of B. It knows t has type parameter [Option[U], V] where U and V are both generic wrt method and are obtained from argument. It tries to initialize B based on the agrument. If the argument was new B[String] it tries deriving B[String] and hence U is automatically obtained as Option[Unit]. If the argument was new A[Option[Int],String] then it obviously it wont match.
The difference has to do with what information the compiler and runtime have in each case, combined with what the restrictions on the types are.
Below the ambiguity is clarified by having U be the trait and class parameter, and X be the method type paramter.
sealed trait A[U]
case class B(u: Unit) extends A[Unit]
class Test[U]() {
def test(t: A[U]) = t match {
case B(u) => u // constructor cannot be instantiated to expected type; found : B required: A[U]
}
}
object Test2 {
def test2[X](t: A[X]) = t match {
case B(x) => x // This works
}
def test3[X] = {
def test(t: A[X]) = t match {
case B(x) => x // This works
}
}
}
Test2.test2(new B(println("yo")))
In Test2.test2, the compiler knows that an instance of A[X] will be provided, with no limits on X.It can generate code that inspects the parameter provided to see if it is B, and handle the case.
In class Test method test, the compiler knows not that some A[X] is provided when called, but that some specific type, U, is presented. This type U can be anything. However, the pattern match is on an algebraic data type. This data type has exactly one valid variation, of type A[Unit]. But this signature is for A[U] where U is not limited do Unit. This is a contradiction. The class definition says that U is a free type parameter, the method says it is Unit.
Imagine you instantiate Test:
val t = new Test[Int]()
Now, at the use site, the method is:
def test(t: A[Int])
There is no such type A[Int]. Pattern matching on B is when the compiler inspects this condition. A[U] for any U is incompatible with the type, hence "constructor cannot be instantiated to expected type; found : B required: A[U]"
The difference with the method versus class type parameter is that one is bound while one is free, at the time the method is called in the runtime.
Another way to think about it is that defining the method
def test(t: A[U])
implies that U is Unit, which is inconsistent with the class declaration that U is anything.
Edit: this is not an answer to the question. I'm keeping it for reference (and the comments).
The link you provided already gives the answer.
In the case of the parameterised method, U is infered from the argument of the actual method call. So, the fact that the case B() was chosen implies that U >: Unit (otherwise the method could not have been called with a B) and the compiler is happy.
In the case of the parameterized class, U is independent from the method argument. So, the fact that the case B() was chosen tells the compiler nothing about U and it can not confirm that U >: Unit. I think if you add such a type bound to U it should work. I haven't tried it, though.
The compiler is absolutely correct with its error message. An easy example should describe the issue well:
sealed trait A[U]
class B extends A[Unit]
class T[U] {
val b: A[Unit] = new B
f(b) // invalid, the compiler can't know which types are valid for `U`
g(b) // valid, the compiler can choose an arbitrary type for `V`
def f(t: A[U]) = g(t)
def g[V](t: A[V]) = ???
}
f and g have different type signatures. g takes an A[V] where V is an arbitrary type parameter. On the other hand, f takes an A[U] where U is not arbitrary but dependent from the outer class T.
The compiler does not know what type U can be at the moment when it typechecks T - it needs to typecheck an instantiation of T to find out which types are used. One can say that U is a concrete type inside of T, but a generic type outside of it. This means it has to reject every code fragment that gets inside of T more concrete about the type of U.
Calling g from inside f is clearly allowed - after all an arbitrary type for V can be chosen, which is U in this case. Because U is never again referenced in g it doesn't matter what type it may have.
Your other code example underlies the same limitations - it is just an example that is more complex.
Btw, that this code
def test3[U] = {
def test(t: A[U]) = t match {
case B() => ??? // This works
}
}
is valid looks weird to me. It doesn't matter if U is bound by a class or a method - from inside of the binding scope we can't give any guarantees about its type, therefore the compiler should reject this pattern match too.
Related
This question already has answers here:
Contravariance vs Covariance in Scala
(3 answers)
Closed 2 years ago.
I have a confusion in understanding covariance type being restricted in method parameters. I read through many materials and I am unable to get them the below concept.
class SomeThing[+T] {
def method(a:T) = {...} <-- produces error
}
In the above piece of code, a is of type T. Why can we not pass subtypes of T? All the expectations of method on parameter x, can be fulfilled by subtype of T perfectly.
Similarly when we have contravariant type T (-T), it can not be passed as method argument; but it is allowed. Why I think it can not be passed is: for e.g, say method invokes a method (present in object a)
on a which is present in T. When we pass super type of T, it may NOT be present. But it is allowed by compiler. This confuses me.
class SomeThing[-T] {
def method(a:T) = {...} <-- allowed
}
So by looking at the above, it is covariant that should be allowed in method arguments as well as in the return type. Contravariant can not be applied.
Can someone please help me to understand.
The key thing about variance is that it affects how the class looks from the outside.
Covariance says that an instance of SomeThing[Int] can be treated as an instance of SomeThing[AnyVal] because AnyVal is a superclass of Int.
In this case your method
def method(a: Int)
would become
def method(a: AnyVal)
This is clearly a problem because you can now pass a Double to a method of SomeThing[Int] that should only accept Int values. Remember that the actual object does not change, only the way that it is perceived by the type system.
Contravariance says that SomeThing[AnyVal] can be treated as SomeThing[Int] so
def method(a: AnyVal)
becomes
def method(a: Int)
This is OK because you can always pass an Int where AnyVal is required.
If you follow through the logic for return types you will see that it works the other way round. It is OK to return covariant types because they can always be treated as being of the superclass type. You can't return contravariant types because the return type might be a subtype of the actual type, which cannot be guaranteed.
I think you are attacking the problem backwards. The fact that you can't have a:T as an argument of a method if T is covariant comes as a constraint because otherwise some illogical code would be completely valid
class A
class B extends A
class C extends B
val myBThing = new SomeThing[B]
Here, myBThing.method accepts a B, and you are right that we can pass it anything that extends B, so myBThing.method(new C) is completely fine. However, myBThing.method(new A) isn't!
Now, since we've defined SomeThing with a covariant, I can also write this
val myAThing: SomeThing[A] = myBThing // Valid since B <: A entails SomeThing[B] <: Something[A] by definition of covariance
myAThing.method(new A) // What? You're managing to send an A to a method that was implemented to receives B and subtypes!
You can see now why we impose the constraint of not passing T as a parameter (parameters are in a "contravariant position").
We can make a similar argument for contravariance in the return position. Remember that contravariance means B <: A entails ``SomeThing[A] <: Something[B]`.
Assume you're defining the following
class A
class B extends A
class SomeThingA[-T](val value: T) // Compiler won't like T in a return type like myThing.value
// If the class definition compiled, we could write
val myThingA: SomeThing[A] = new SomeThing(new A)
val someA: A = myThingA.value
val myThingB: SomeThing[B] = myThingA // Valid because T contravariant
val someB: B = myThingB.value // What? I only ever stored an A!
For more details, see this answer.
In the case of class SomeThing[T], placing a + or - before the T actually effects the class itself more than the type parameter.
Consider the following:
val instanceA = new SomeThing[A]
val instanceB = new SomeThing[B]
If SomeThing is invariant on T (no + or -) then the instances will have no variance relationship.
If SomeThing is covariant on T ([+T]) then the instances will have the same variance relationship as A and B have. In other words, if A is a sub-type of B (or vice versa) then the instances will reflect that same relationship.
If SomeThing is contravariant on T ([-T]) then the instances will have the opposite variance relationship as A and B have. In other words, if A is a sub-type of B then instanceB will be a sub-type of instanceA.
But the variance indicator does effect how the type parameter can be used. If T is marked + then it can't be placed in a contravariant position and, likewise, if marked - then it can't be placed in a covariant position. We bump up against this most often when defining methods.
Scala methods are very closely related to the Scala function traits: Function0, Function1, Function2, etc.
Consider the definition of Function1:
trait Function1[-T1, +R] extends AnyRef
Now let's say you want to pass a function of this type around.
def useThisFunc(f: A => B):Unit = {...}
Because a Function1 is contravariant on its received parameter and covariant on its result, all of the following are acceptable as a useThisFunc() parameter.
val a2b : A => B = ???
val supa2b : SuperOfA => B = ???
val a2subb : A => SubOfB = ???
val supa2subb : SuperOfA => SubOfB = ???
So, in conclusion, if SomeThing is covariant on T then you can't have T as a passed parameter of a member method because FunctionX is contravariant on its parameter types. Likewise, if SomeThing is contravariant on T the you can't have T as member method return type because FunctionX is covariant on its return type.
I think, my issue is best described by some sample code:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X { def f: Foo[_] }
case class Wrapper[D](f: Foo[D]) extends X
val w: X = Wrapper(new Bar)
w match { case Wrapper(_: Bar) => 1 }
The last line fails with
found : Bar
required: Foo[Any]
Note: String <: Any (and Bar <: Foo[String]), but class Foo is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
I understand that this happens because unapply is defined with a type parameter, which is inferred as Any, and so it complains about String being incompatible.
But the question is if there is any way to make it work? I tried giving a type parameter to the exctractor, like this: w match { case Wrapper[String](_: Bar) => 1 }, but it says it does not take parameters (which is a lie) ... :(
The only way I came up with so far is this ugly baby:
w match { case w: Wrapper[String] if w.f.isInstanceOf[Bar] => 1 }
or, maybe,
Option(w).map(_.f) match { case Some(_: Bar) => 1 }
(the latter works because Option is covariant, but I can't make my classes covariant unfortunately). Also, I can't really use the last alternative without some additional ugliness IRL, because the equivalent of X in real life doesn't actually have f.
Any better ideas?
Define custom extractor
Wrapper.unapply does take type parameter, but you cannot specify one* in a pattern match sequence, so compiler infers one for you (and if compiler does that, it's very often Any or Nothing).
And, actually, you don't want it to, because you're removing your type information when you coerce your element to type X. So, you want a matcher for existential version
object WrapperEx {
def unapply(w: Wrapper[_]): Option[Foo[_]] = Wrapper.unapply(w)
}
And use it like so:
w match { case WrapperEx(_: Bar) => 1 }
Runnable version here
Good news: you can delegate to generated case class matcher.
Bad news: you cannot define it inside case class companion. Scala is happily picking the wrong one already, so it won't be able to disambiguate.
Still, I'd say it's not half-bad
* you can in latest Typelevel Scala, but I'm not sure how it works with type casts and I could not get it to work for your case.
You can parameterize trait X to get rid of existential type in def f: Foo[_], I think this is what trips the compiler up. The following code works:
class Foo[T]
class Bar extends Foo[String]
class Baz extends Foo[Int]
trait X[A] { def f: Foo[A] }
case class Wrapper[D](f: Foo[D]) extends X[D]
val w: X[String] = Wrapper(new Bar) // type ascription can be omitted and will be inferred
w match { case Wrapper(_: Bar) => 1 }
This is somehow unexpected in scala(or my expectation is wrong). So following are some sample code:
trait A
trait B
trait HasA { def foo: A }
trait HasB { def foo: B }
so far so good, then i do
scala> trait HasBoth extends HasA with HasB
<console>:11: error: overriding method foo in trait HasA of type => A;
method foo in trait HasB of type => B has incompatible type
trait HasBoth extends HasA with HasB
Alright, I see the error. it makes sense.
but the confusion comes following:
trait Wrapper[T] { def get: T }
trait HasBoth2 { self: Wrapper[HasA with HasB] => def bar = get.foo }
this I found it's insane. if we check the type of bar, its B. if we flip the order, such that the constraint is HasB with HasA, then bar is A.
so we have some inconsistency here. I would expect either of:
HasA with HasB yield { def foo: A with B } automatically, or
HasBoth2 yields a type incompatibility error.
please kindly explain this inconsistency, or it's a type system bug.
Update
trait HasBoth3 extends HasA with HasB { def foo: A with B }
type Wrapped = Wrapper[HasBoth3]
Wrapped will satisfy the constraint of HasBoth2.
This is the expected behavior. It is one of scala's core points of unsoundness.
SI-7278: Unsoundess in many guises
I offer some explanation in SI-7255, reproduced in part below.
This seems to be a pretty fundamental failure of the type system, and maybe part of the motivation for dotty.
Abstract type members aren't unified as normally implied by the word; they can only be refined. What that means in this context is that a type is created which is the intersection of B with its self type, that is, B with A. The intersection type is created assuming it can just use the last 'C' in the linearization, since the compile will fail in refchecks unless the last 'C' is a refinement of any earlier 'C's.
This system breaks down with self types, because a class sees itself as having the intersection of the declared class and the declared self type. Either X with Y or Y with X might be valid instantiations; there is no way a priori to know which is the "last" member C.
For yet more color, see my last comment on SI-7472.
I have some collection of type Collection[SuperType]. Stored in this collection are several values that are subtypes of SuperType, I would like the collection to only allow itself to contain one instance of each subtype (A bit like a set, but not).
I am trying to write a function that when given the companion object of one of the above mentioned subtypes, can return the first instance of the class the companion object belongs to.
Initially I tried with a Set as shown below, but T will suffer from type erasure, so the pattern matching will fail. I then also realised a Set wasn't suitable for this task, because I only want one occurrence of each subtype in the collection.
def get[T <: SuperType](target: T): Option[SuperType] =
collection.collectFirst({
case target: T => target
})
My next, and current approach is using a map, where the key is a companion object and the value is an instance of the companion object's class. The type hierarchy is show below.
trait SuperType
trait SuperTypeValue
// Pretend this has some parameters
case class ExampleSubType extends SuperTypeValue
case object ExampleSubType extends SuperType {
// value for use in later example
val uniqueToObjectField: String = "hello"
}
val collection: Map[SuperType, SuperTypeValue] = // Some new map
def get(target: SuperType): Option[SuperTypeValue] =
collection.get(target)
The above works well enough. However, I would like to preserve the type of the subtype that is used as the parameter, and use it as the return type. I believe the signature of the function would look something like this:
get[T <: SuperType](target: T): Option[T]
// So I could then do something like this
get(ExampleSubType) match {
case Some(exampleSubType) => exampleSubType.uniqueToObjectField
case _ => "nope"
}
Is this possible within scala? If so, how? If not, does this exist in other languages and what is it called?
Hopefully there are no glaring issues with this question, but it's 2am so I shall check things over again in the morning.
You can use ClassTags to get around the type erasure. Instead of using the companion object, it might be easier to just provide the generic parameter explicitly:
import scala.reflect._
trait SuperType { val x: Int }
case class Foo(x: Int) extends SuperType
case class Bar(x: Int) extends SuperType
val collection = Set(Foo(1), Foo(2), Bar(3), Foo(4), Bar(5))
def get[T <: SuperType : ClassTag]: Option[T] = {
collection.collectFirst {
case target: T => target
}
}
And then you can call:
get[Foo] //Foo(1)
get[Bar] //Bar(3)
You are trying to use a hammer when you need a saw. You should just make a new class for this with a field for each type.
class SomeClass{
a:TypeA
b:TypeB
c:TypeC
// if c extends b extends a maybe you want
// to prevent a TypeC being assigned to A I have no idea
// you can change these implementations to get the behavior you want
addA(a:TypeA){ this.a = a}
addB(b:TypeB){ this.b = b}
addC(c:TypeC){ this.c = c}
}
Often novices try use collections for crazy purposes. Just because a collection holds data, doesn't mean any time you want to hold data you need one. You need to think about what your requirements are first before you decide what you are going to use, not the other way around, you will be on SO for the rest of your programming life if you take that approach.
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 to Int, 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
old class 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 not T[_, 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:
It is used for methods which do not care about the type parameter
It is used for methods where you want to express that one type parameter is a type constructor.
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
}
}