Что означают <: <, <% <и =: = в Scala 2.8 и где они задокументированы?

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

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

196
задан Mario Galic 16 June 2019 в 02:50
поделиться

3 ответа

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

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] ]. По сути, вы пишете специальный код для определенных типов списков.

207
ответ дан 23 November 2019 в 05:19
поделиться

Это не полный ответ (другие уже ответили на этот вопрос), я просто хотел отметить следующее, что, возможно, поможет лучше понять синтаксис: То, как вы обычно используете эти "операторы", как, например, в примере 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")

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

54
ответ дан 23 November 2019 в 05:19
поделиться

Это зависит от того, где они используются. Чаще всего при объявлении типов неявных параметров они являются классами. В редких случаях они тоже могут быть объектами. Наконец, они могут быть операторами для объектов 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

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

17
ответ дан 23 November 2019 в 05:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: