Я вижу в документации API для Predef , что они являются подклассами универсального типа функции (From) => To, но это все это говорит. Гм, что? Возможно, где-то есть документация, но поисковые системы не очень хорошо обрабатывают "имена" типа "<: <", поэтому я не смог ее найти.
Последующий вопрос: когда мне следует использовать эти забавные символы / классы и почему?
Они называются ограничениями обобщенного типа . Они позволяют вам изнутри класса или признака с параметризацией типа дополнительно ограничивать один из его параметров типа. Вот пример:
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
}
Неявный аргумент свидетельство
предоставляется компилятором, если 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 должно быть точно B A <: означает, что A должен быть подтипом B (аналогично ограничению типа simple <:
])
A <% означает, что A должен быть видимый как B, возможно, через неявное преобразование (аналогично ограничению простого типа <%
)
Этот фрагмент от @retronym является хорошим объяснением того, как такого рода то, что раньше было достигнуто, и как ограничения обобщенного типа упрощают это сейчас.
ДОБАВЛЕНИЕ
Чтобы ответить на ваш дополнительный вопрос, по общему признанию, приведенный мной пример довольно надуманный и не очевидно полезный. Но представьте, что вы используете его для определения чего-то вроде метода List.sumInts
, который складывает список целых чисел. Вы не хотите, чтобы этот метод был вызван для любого старого List
, только для List [Int]
. Однако конструктор типа List
не может иметь таких ограничений; вы по-прежнему хотите иметь списки строк, символов, полос и прочего.Таким образом, поместив ограничение обобщенного типа на 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")
Это похоже на два синтаксиса для вызова методов, "обычный" с .
и ()
и синтаксис оператора.
Это зависит от того, где они используются. Чаще всего при объявлении типов неявных параметров они являются классами. В редких случаях они тоже могут быть объектами. Наконец, они могут быть операторами для объектов Manifest
. В первых двух случаях они определены внутри scala.Predef
, хотя и не особо хорошо документированы.
Они предназначены для проверки взаимосвязи между классами, как и <:
и <%
, в ситуациях, когда последний не может быть использован.
Что касается вопроса «когда мне их использовать?», То ответ - не следует, если только вы не знаете, что должны. :-) РЕДАКТИРОВАТЬ : Хорошо, хорошо, вот несколько примеров из библиотеки. На Либо
, у вас есть:
/**
* 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)
}
На Вариант
, у вас есть:
def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null
Вы найдете другие примеры в коллекциях.