Zava
Zava

Reputation: 773

what's different between <:< and <: in scala

I already know that:

for 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

Answers (4)

gourlaysama
gourlaysama

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:

    1. 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.

    2. 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

Yuriy
Yuriy

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
  • if we not call methods what not conform <:< condition than all compile and execute.

Upvotes: 7

drexin
drexin

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

Zava
Zava

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:

  • the scope of <: 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]
  • the scope of <:< , 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

Related Questions