Что означают <: <, <% <, и =: = среднее значение в Scala 2.8, и где они задокументированы?
В документации по API для Predef я вижу, что они являются подклассами универсального типа функции (From) => To, но этим все сказано. Хм что? Может быть, где-то есть документация, но поисковые системы не очень хорошо обрабатывают такие "имена", как "<: <", поэтому я не смог их найти.
Дополнительный вопрос: когда я должен использовать эти классные символы/классы и почему?
Ответы
Ответ 1
Они называются обобщенными ограничениями типа. Они позволяют вам, из класса или свойства, параметризованного по типу, дополнительно ограничить один из его параметров типа. Вот пример:
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
предоставляется компилятором, iff 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 <:< B
означает, что A должен быть подтипом B (аналогичным простому типу <:
)
-
A <%< B
означает, что A должен быть доступен для просмотра как B, возможно через неявное преобразование (аналогично простому типу <%
)
Этот фрагмент от @retronym является хорошим объяснением того, как это делалось раньше и как обобщенные ограничения типов облегчают сейчас.
ДОПОЛНЕНИЕ
Чтобы ответить на ваш последующий вопрос, по общему признанию, пример, который я дал, довольно надуманный и явно не полезный. Но представьте, используя его, чтобы определить что-то вроде метода List.sumInts
, который добавляет список целых чисел. Вы не хотите, чтобы этот метод вызывался в любом старом List
, просто List[Int]
. Однако конструктор типа List
не может быть так ограничен; вы все равно хотите иметь списки строк, foos, bars и whatnots. Поэтому, помещая ограничение обобщенного типа на sumInts
, вы можете убедиться, что только этот метод имеет дополнительное ограничение, которое можно использовать только в List[Int]
. По сути, вы пишете специальный код для определенных видов списков.
Ответ 2
Не полный ответ (другие уже ответили на это), я просто хотел бы отметить следующее, что, возможно, поможет лучше понять синтаксис: как обычно вы используете эти "операторы", например, в примере с пелотомом:
def getStringLength(implicit evidence: A =:= String)
использует Scala альтернативный синтаксис infix для операторов типа.
Итак, A =:= String
совпадает с =:=[A, String]
(и =:=
- это просто класс или признак с фантастическим именем). Обратите внимание, что этот синтаксис также работает с "обычными" классами, например, вы можете написать:
val a: Tuple2[Int, String] = (1, "one")
вот так:
val a: Int Tuple2 String = (1, "one")
Он похож на два синтаксиса для вызовов методов: "нормальный" с .
и ()
и синтаксис оператора.
Ответ 3
Прочитайте другие ответы, чтобы понять, что это за конструкции. Здесь , когда вы должны их использовать. Вы используете их, когда вам нужно ограничить метод только для определенных типов.
Вот пример. Предположим, вы хотите определить однородную пару, например:
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
Это нормально, чтобы создать экземпляр, скажем, < <28 > , если вы не назовете smaller
на нем.
В случае Option
разработчикам нужен метод orNull
, хотя это не имеет смысла для Option[Int]
. Используя ограничение типа, все хорошо. Вы можете использовать orNull
на Option[String]
, и вы можете создать Option[Int]
и использовать его, если вы не вызываете orNull
на нем. Если вы попробуете Some(42).orNull
, вы получите очаровательное сообщение
error: Cannot prove that Null <:< Int
Ответ 4
Это зависит от того, где они используются. Чаще всего при использовании при объявлении типов неявных параметров они являются классами. Они также могут быть объектами в редких случаях. Наконец, они могут быть операторами на объектах 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
В сборниках вы найдете несколько других примеров.
Ответ 5
В Scala 2.13 они были Predef
из Predef
: Move <: <, =: =, DummyImplicits из Predef # 7350
Особенностью ограничений типов, которые могли бы не быть явными в других ответах, является то, что они могут использоваться для
... ограничить любой абстрактный тип 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] =
...
}