Что означают <: <, <% <и =: = в Scala 2.8 и где они документированы? - PullRequest
192 голосов
/ 07 августа 2010

В документации API для Predef я вижу, что они являются подклассами универсального типа функции (From) => To, но это все, что они говорят.Хм что?Может быть, где-то есть документация, но поисковые системы не очень хорошо обрабатывают «имена», например «<: <», поэтому я не смог ее найти. </p>

Последующий вопрос: когда мне следует использоватьэти классные символы / классы и почему?

Ответы [ 5 ]

202 голосов
/ 07 августа 2010

Это так называемые ограничения обобщенного типа . Они позволяют вам из класса с параметризованным типом или признака дополнительно ограничить один из его параметров типа. Вот пример:

case class Foo[A](a:A) { // 'A' can be substituted with any type
    // getStringLength can only be used if this is a Foo[String]
    def getStringLength(implicit evidence: A =:= String) = a.length
}

Неявный аргумент evidence предоставляется компилятором, если A равен String. Вы можете думать об этом как о доказательстве , что A есть String - сам аргумент не важен, только зная, что он существует. [править: технически это действительно важно, потому что оно представляет собой неявное преобразование из A в String, что позволяет вам вызывать a.length, а компилятор не кричит на вас]

Теперь я могу использовать его так:

scala> Foo("blah").getStringLength
res6: Int = 4

Но если я попробую использовать его с Foo, содержащим что-то отличное от String:

scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]

Вы можете прочитать эту ошибку как "не удалось найти доказательства того, что Int == String" ... так и должно быть! getStringLength налагает дополнительные ограничения на тип A, чем требуется Foo в целом; а именно, вы можете вызывать getStringLength только на Foo[String]. Это ограничение применяется во время компиляции, и это здорово!

<:< и <%< работают аналогично, но с небольшими вариациями:

  • A =:= B означает, что A должно быть точно B
  • A <:< B означает, что A должен быть подтипом B (аналог ограничения по типу simple <:)
  • A <%< B означает, что A должен быть видимым как B, возможно посредством неявного преобразования (аналогично ограничению простого типа <%)

Этот фрагмент от @retronym является хорошим объяснением того, как такого рода вещи выполнялись раньше, и как обобщенные ограничения типов облегчают его выполнение.

ДОПОЛНЕНИЕ

Чтобы ответить на ваш дополнительный вопрос, по общему признанию, приведенный мною пример довольно надуманен и явно не полезен. Но представьте, что вы используете его, чтобы определить что-то вроде List.sumInts метода, который добавляет список целых чисел Вы не хотите, чтобы этот метод вызывался на любом старом List, просто на List[Int]. Однако конструктор типа List не может быть таким ограниченным; вы все еще хотите иметь возможность иметь списки строк, foos, баров и еще много чего. Таким образом, поместив ограничение обобщенного типа на sumInts, вы можете гарантировать, что только для этого метода имеет дополнительное ограничение, которое может использоваться только на List[Int]. По сути, вы пишете специальный код для определенных типов списков.

53 голосов
/ 07 августа 2010

Не полный ответ (другие уже ответили на это), я просто хотел отметить следующее, что, возможно, поможет лучше понять синтаксис: как вы обычно используете эти «операторы», как, например, в примере с pelotom: 1001 *

def getStringLength(implicit evidence: A =:= String)

использует альтернативный инфиксный синтаксис Scala для операторов типов .

Итак, A =:= String - это то же самое, что и =:=[A, String]=:= - это просто класс или черта с причудливым названием). Обратите внимание, что этот синтаксис также работает с «обычными» классами, например, вы можете написать:

val a: Tuple2[Int, String] = (1, "one")

как это:

val a: Int Tuple2 String = (1, "one")

Он похож на два синтаксиса для вызовов методов: "обычный" с . и () и синтаксис оператора.

38 голосов
/ 12 августа 2011

Прочтите другие ответы, чтобы понять, что это за конструкции. Вот когда вы должны их использовать. Вы используете их, когда вам нужно ограничить метод только для определенных типов.

Вот пример. Предположим, вы хотите определить однородную пару, например:

class Pair[T](val first: T, val second: T)

Теперь вы хотите добавить метод smaller, например:

def smaller = if (first < second) first else second

Это работает, только если заказан T. Вы можете ограничить весь класс:

class Pair[T <: Ordered[T]](val first: T, val second: T)

Но это, кажется, позор - может быть использование для класса, когда T не заказан. С помощью ограничения типа вы все равно можете определить метод smaller:

def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second

Можно создать экземпляр, скажем, Pair[File], , если вы не наберете smaller.

В случае Option разработчики хотели метод orNull, хотя он не имеет смысла для Option[Int]. Используя ограничение типа, все хорошо. Вы можете использовать orNull на Option[String], и вы можете сформировать Option[Int] и использовать его, пока вы не наберете orNull на нем. Если вы попробуете Some(42).orNull, вы получите очаровательное сообщение

 error: Cannot prove that Null <:< Int
17 голосов
/ 07 августа 2010

Это зависит от того, где они используются.Чаще всего, когда они используются при объявлении типов неявных параметров, они являются классами.Они могут быть объектами тоже в редких случаях.Наконец, они могут быть операторами для Manifest объектов.Они определены внутри scala.Predef в первых двух случаях, хотя и не особенно хорошо документированы.

Они предназначены для обеспечения способа проверки отношений между классами, как <: и <% doв ситуациях, когда последнее не может быть использовано.

Что касается вопроса «когда я должен их использовать?», то ответ - не стоит, если только вы не знаете, что должны.:-) EDIT : Хорошо, хорошо, вот несколько примеров из библиотеки.На Either у вас есть:

/**
  * Joins an <code>Either</code> through <code>Right</code>.
  */
 def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
   case Left(a)  => Left(a)
   case Right(b) => b
 }

 /**
  * Joins an <code>Either</code> through <code>Left</code>.
  */
 def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
   case Left(a)  => a
   case Right(b) => Right(b)
 }

На Option у вас есть:

def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null

Другие коллекции вы найдете в коллекциях.

0 голосов
/ 16 июня 2019

В Scala 2.13 они были перемещены из Predef: Move <: <, =: =, DummyImplicits из Predef # 7350 </a>

A особенность ограничений типа, которые могли бы не быть явными в других ответах, если их можно использовать для

... ограничение любой абстрактный тип T это в области действия в методе список аргументов ( не только собственные параметры типа метода )

Вот пример, иллюстрирующий «не только собственные параметры типа метода» . Скажем, у нас есть

case class Foo[A, B](f: A => B) {
  def bar[C <: A](x: C)(implicit e: B <:< String): B = f(x)
}

Foo[Int, String](x => x.toString).bar(1) // OK.
Foo[Int, Double](x => x.toDouble).bar(1) // error: Cannot prove that Double <:< String.

Обратите внимание, как мы можем ограничить параметр типа B, несмотря на тот факт, что он не появляется в предложении bar параметра типа [C <: A]. Если вместо этого мы попытаемся ограничить B в условии параметра типа bar, например,

def bar[B <: String]

мы будем использовать параметр типа теневого копирования B из области охвата Foo[A, B]. Реальным примером этого из библиотеки будет toMap:

trait IterableOnceOps[+A, +CC[_], +C] extends Any {
  ...
  def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] =
  ...
}
...