Reputation: 773
I already know that:
<:
is the Scala syntax type constraint<:<
is the type that leverage the Scala implicit to reach the type constraitfor example:
object Test {
// the function foo and bar can have the same effect
def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i
foo(1) // compile error
foo("hi")
def bar[A <: java.io.Serializable](i:A) = i
bar(1) // compile error
bar("hi")
}
but I want to know when we need to use <:
and <:<
?
and if we already have <:
, why we need <:<
?
thanks!
Upvotes: 16
Views: 562
Reputation: 11290
There are definitely differences between <:
and <:<
; here is my attempt at explaining which one you should pick.
Let's take two classes:
trait U
class V extends U
The type constraint <:
is always used because it drives type inference. That's the only thing it can do: constrain the type on its left-hand side.
The constrained type has to be referenced somewhere, usually in the parameter list (or return type), as in:
def whatever[A <: U](p: A): List[A] = ???
That way, the compiler will throw an error if the input is not a subclass of U
, and at the same time allow you to refer to the input's type by name for later use (for example in the return type). Note that if you don't have that second requirement, all this isn't necessary (with exceptions...), as in:
def whatever(p: U): String = ??? // this will obviously only accept T <: U
The Generalized Type Constraint <:<
on the other hand, has two uses:
You can use it as an after-the-fact proof that some type was inferred. As in:
class List[+A] {
def sum(implicit ev: A =:= Int) = ???
}
You can create such a list of any type, but sum
can only be called when you have the proof that A
is actually Int
.
You can use the above 'proof' as a way to infer even more types. This allows you to infer types in two steps instead of one.
For example, in the above List
class, you could add a flatten
method:
def flatten[B](implicit ev: A <:< List[B]): List[B]
This isn't just a proof, this is a way to grab that inner type B
with A
now fixed.
This can be used within the same method as well: imagine you want to write a utility sort
function, and you want both the element type T
and the collection type Coll
. You could be tempted to write the following:
def sort[T, Coll <: Seq[T]](l: Coll): Coll
But T
isn't constrained to be anything in there: it doesn't appear in the arguments nor output type. So T
will end up as Nothing
, or Any
, or whatever the compiler wants, really (usually Nothing
). But with this version:
def sort[T, Coll](l: Coll)(implicit ev: Coll <:< Seq[T]): Coll
Now T
appears in the parameter's types. There will be two inference runs (one per parameter list): Coll
will be inferred to whatever was given, and then, later on, an implicit will be looked for, and if found, T
will be inferred with Coll
now fixed. This essentially extracts the type parameter T
from the previously-inferred Coll
.
So essentially, <:<
checks (and potentially infers) types as a side-effect of implicit resolution, so it can be used in different places / at different times than type parameter inference. When they happen to do the same thing, stick to <:
.
Upvotes: 3
Reputation: 2769
1. def bar[A <: java.io.Serializable](i:A) = i
<: - guarantees that instance of i of type parameter A will be subtype of Serializable
2. def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i
<:< - guarantees that execution context will contains implicit value (for ev paramenter) of type A what is subtype of Serializable. This implicit defined in Predef.scala and for foo method and it is prove if instance of type parameter A is subtype of Serializable:
implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
fictional case of using <:< operator:
class Boo[A](x: A) {
def get: A = x
def div(implicit ev : A <:< Double) = x / 2
def inc(implicit ev : A <:< Int) = x + 1
}
val a = new Boo("hi")
a.get // - OK
a.div // - compile time error String not subtype of Double
a.inc // - compile tile error String not subtype of Int
val b = new Boo(10.0)
b.get // - OK
b.div // - OK
b.inc // - compile time error Double not subtype of Int
val c = new Boo(10)
c.get // - OK
c.div // - compile time error Int not subtype of Double
c.inc // - OK
Upvotes: 7
Reputation: 24403
The main difference between the two is, that the <:
is a constraint on the type, while the <:<
is a type for which the compiler has to find evidence, when used as an implicit parameter. What that means for our program is, that in the <:
case, the type inferencer will try to find a type that satisfies this constraint. E.g.
def foo[A, B <: A](a: A, b: B) = (a,b)
scala> foo(1, List(1,2,3))
res1: (Any, List[Int]) = (1,List(1, 2, 3))
Here the inferencer finds that Int
and List[Int]
have the common super type Any
, so it infers that for A
to satisfy B <: A
.
<:<
is more restrictive, because the type inferencer runs before the implicit resolution. So the types are already fixed when the compiler tries to find the evidence. E.g.
def bar[A,B](a: A, b: B)(implicit ev: B <:< A) = (a,b)
scala> bar(1,1)
res2: (Int, Int) = (1,1)
scala> bar(1,List(1,2,3))
<console>:9: error: Cannot prove that List[Int] <:< Int.
bar(1,List(1,2,3))
^
Upvotes: 21
Reputation: 773
After some thinking, I think it has some different. for example:
object TestAgain {
class Test[A](a: A) {
def foo[A <: AnyRef] = a
def bar(implicit ev: A <:< AnyRef) = a
}
val test = new Test(1)
test.foo // return 1
test.bar // error: Cannot prove that Int <:< AnyRef.
}
this menas:
<:
is just in the method param generic tpye scope foo[A <: AnyRef]
. In the example, the method foo
have it's generic tpye A
, but not the A
in class Test[A]
<:<
, will first find the method's generic type, but the method bar
have no param generic type, so it will find the Test[A]
's generic type.so, I think it's the main difference.
Upvotes: 1