Что означают <: <, <% <и =: = в Scala 2.8 и где они документированы?
В документации по API для Predef я вижу, что они являются подклассами универсального типа функции (From) => To, но это все, что они говорят. Хм что? Может быть, где-то есть документация, но поисковые системы не очень хорошо обрабатывают такие "имена", как "<: <", поэтому я не смог их найти.
Дополнительный вопрос: когда я должен использовать эти классные символы / классы и почему?
4 ответа
Это так называемые ограничения обобщенных типов. Они позволяют вам изнутри параметризованного типа класса или признака дополнительно ограничить один из его параметров типа. Вот пример:
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
- сам аргумент не важен, только зная, что он существует. [edit: ну, технически это действительно важно, потому что оно представляет собой неявное преобразование из 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 должен быть точно BA <:< B
означает, что A должен быть подтипом B (аналогично ограничению простого типа<:
)A <%< B
означает, что A должен просматриваться как B, возможно, посредством неявного преобразования (аналогично ограничению простого типа<%
)
Этот фрагмент @retronym является хорошим объяснением того, как такого рода вещи выполнялись раньше, и как обобщенные ограничения типов делают это проще.
ДОПОЛНЕНИЕ
Чтобы ответить на ваш дополнительный вопрос, по общему признанию, приведенный мною пример довольно надуманен и явно не полезен. Но представьте, что вы используете его, чтобы определить List.sumInts
метод, который складывает список целых чисел. Вы не хотите, чтобы этот метод вызывался на любом старом List
просто List[Int]
, Тем не менее List
конструктор типа не может быть таким ограниченным; вы все еще хотите иметь возможность иметь списки строк, foos, баров и еще много чего. Таким образом, поместив обобщенное ограничение типа на sumInts
вы можете убедиться, что только этот метод имеет дополнительное ограничение, что он может быть использован только на List[Int]
, По сути, вы пишете специальный код для определенных типов списков.
Не полный ответ (другие уже ответили на это), я просто хотел отметить следующее, что, возможно, помогает лучше понять синтаксис: как вы обычно используете эти "операторы", как, например, в примере с pelotom:
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")
Это похоже на два синтаксиса для вызовов методов, "нормальный" с .
а также ()
и синтаксис оператора.
Прочитайте другие ответы, чтобы понять, что это за конструкции. Вот когда вы должны их использовать. Вы используете их, когда вам нужно ограничить метод только для определенных типов.
Вот пример. Предположим, вы хотите определить однородную пару, например:
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
Это зависит от того, где они используются. Чаще всего, когда они используются при объявлении типов неявных параметров, они являются классами. Они могут быть объектами тоже в редких случаях. Наконец, они могут быть операторами на Manifest
объекты. Они определены внутри scala.Predef
в первых двух случаях, хотя и не особенно хорошо задокументировано.
Они призваны обеспечить способ проверить отношения между классами, как <:
а также <%
делать в ситуациях, когда последний не может быть использован.
Что касается вопроса "когда я должен их использовать?", Ответ - не стоит, если только вы не знаете, что должны.:-) РЕДАКТИРОВАТЬ: Хорошо, хорошо, вот несколько примеров из библиотеки. На 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
Вы найдете несколько других примеров в коллекциях.