Что отличается между <: <и <: in scala

Я уже знаю, что:

  • <: - это ограничение типа синтаксиса Scala
  • while <:< - тип, который использует Scala неявный, чтобы достичь типа contrait

например:

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")
}

но я хочу знать, когда нам нужно использовать <: и <:<?

и если мы уже имеем <:, зачем нам <:<?

спасибо!

Ответ 1

Основное различие между ними состоит в том, что <: является ограничением на тип, тогда как <:< является типом, для которого компилятор должен найти доказательства, когда он используется как неявный параметр. Для этой программы это означает, что в случае <: тип inferencer попытается найти тип, удовлетворяющий этому ограничению. Например.

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))

Здесь оппонент обнаруживает, что Int и List[Int] имеют общий супер-тип Any, поэтому он указывает, что для A удовлетворяет B <: A.

<:< является более ограничивающим, поскольку тип inferencer работает до неявного разрешения. Таким образом, типы уже исправлены, когда компилятор пытается найти доказательства. Например.

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))
                 ^

Ответ 2

1.  def bar[A <: java.io.Serializable](i:A) = i

<: - гарантирует, что экземпляр параметра i параметра A будет подтипом Serializable

2. def foo[A](i:A)(implicit ev : A <:< java.io.Serializable) = i

<: < - гарантирует, что контекст выполнения будет содержать неявное значение (для ev paramenter) типа A, что является подтипом Serializable. Это неявно определено в Predef.scala и для метода foo, и это доказывает, что экземпляр параметра типа A является подтипом Serializable:

implicit def conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]

вымышленный случай использования <: < Оператор:

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
  • если мы не будем называть методы, которые не соответствуют <: < условие, чем все компилировать и выполнять.

Ответ 3

Существуют определенные различия между <: и <:<; вот моя попытка объяснить, какой из них вы должны выбрать.

Возьмем два класса:

trait U
class V extends U
  • Ограничение типа <: всегда используется, потому что оно вызывает вывод типа. Единственное, что он может сделать: ограничить тип в левой части.

    Спрятанный тип должен упоминаться где-то, обычно в списке параметров (или возвращаемом типе), как в:

    def whatever[A <: U](p: A): List[A] = ???
    

    Таким образом, компилятор выдает ошибку, если вход не является подклассом U и в то же время позволяет вам ссылаться на тип ввода по имени для последующего использования (например, в возвращаемом типе), Обратите внимание, что если у вас нет этого второго требования, все это необязательно (с исключениями...), как в:

    def whatever(p: U): String = ??? // this will obviously only accept T <: U
    
  • Обобщенное ограничение типа <:<, с другой стороны, имеет два применения:

    • Вы можете использовать его как доказательство после того, как был выведен какой-то тип. Как в:

      class List[+A] {
        def sum(implicit ev: A =:= Int) = ???
      }
      

      Вы можете создать такой список любого типа, но sum может быть вызван только тогда, когда у вас есть доказательство того, что A на самом деле Int.

    • Вы можете использовать вышеуказанное "доказательство" как способ сделать еще больше типов. Это позволяет вам выводить типы в два этапа вместо одного.

      Например, в вышеприведенном классе List вы можете добавить метод flatten:

      def flatten[B](implicit ev: A <:< List[B]): List[B]
      

      Это не просто доказательство, это способ захватить этот внутренний тип B с A теперь исправленным.

      Это можно использовать и в том же методе: представьте, что вы хотите написать утилиту sort, и вы хотите как тип элемента T, так и тип коллекции Coll. У вас может возникнуть соблазн написать следующее:

      def sort[T, Coll <: Seq[T]](l: Coll): Coll
      

      Но T не ограничивается чем-либо там: он не отображается в аргументах и ​​типе вывода. Таким образом, T закончится как Nothing, или Any, или независимо от того, что хочет компилятор, действительно (обычно Nothing). Но с этой версией:

      def sort[T, Coll](l: Coll)(implicit ev: Coll <:< Seq[T]): Coll
      

      Теперь в типах параметров появляется T. Будут два прогона вывода (по одному для каждого списка параметров): Coll будет выведено на все, что было дано, а затем, впоследствии, будет подразумеваться неявное, и если найдено, T будет выведено с помощью Coll теперь исправлено. Это по существу извлекает параметр типа T из ранее сделанного Coll.

Таким образом, <:< проверяет (и потенциально выводит) типы как побочный эффект неявного разрешения, поэтому его можно использовать в разных местах/в разное время, чем вывод параметра типа. Когда они совершают одно и то же, придерживайтесь <:.

Ответ 4

После некоторого размышления, я думаю, у него есть разные. например:

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

это меню:

  • область <: находится только в методе param generic tpye scope foo[A <: AnyRef]. В примере метод foo имеет общий tpye A, но не A в классе Test[A]
  • область <:<, сначала найдет метод generic type, но метод bar не имеет типа param generic, поэтому он найдет общий тип Test[A].

поэтому, я думаю, это основное отличие.