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

201

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

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

Джефф
источник
6
Вот связанный вопрос, который может хотя бы частично ответить на ваш вопрос: stackoverflow.com/questions/2603003/operator-in-scala
Ярдена
13
symbolhound.com - ваш друг для поиска кода :)
ron
Выполняют ли Haskell typeclassработу этих операторов? Пример: compare :: Ord a => a -> a -> Ordering? Я пытаюсь понять эту концепцию Scala относительно ее аналога на Haskell.
Кевин Мередит

Ответы:

217

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

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само по себе --the аргумент не имеет значения, только зная , что она существует. [править: технически это действительно важно, потому что представляет неявное преобразование из 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, просто a List[Int]. Однако Listконструктор типа не может быть таким ограниченным; вы все еще хотите иметь возможность иметь списки строк, foos, баров и еще много чего. Таким образом, установив ограничение обобщенного типа sumInts, вы можете убедиться, что только у этого метода есть дополнительное ограничение, которое может использоваться только в List[Int]. По сути, вы пишете специальный код для определенных видов списков.

Том Крокетт
источник
3
Хорошо, хорошо, но есть также методы с такими же именами, о Manifestкоторых вы не упомянули.
Даниэль С. Собрал
3
Методы on Manifestis <:<и >:>only ..., поскольку OP упомянул ровно 3 разновидности обобщенных ограничений типов, я предполагаю, что это то, что его интересовало.
Том Крокетт,
12
@IttayD: это довольно умно ... class =:=[From, To] extends From => To, что означает, что неявное значение типа From =:= Toна самом деле является неявным преобразованием из Fromв To. Таким образом, принимая неявный параметр типа, A =:= Stringвы говорите, что он Aможет быть неявно преобразован в String. Если вы изменили порядок и сделали неявный аргумент типа String =:= A, он не сработает, потому что это будет неявное преобразование из Stringв A.
Том Крокетт
25
У этих трехсимвольных символов есть имена? Моя проблема с символическим супом Scala заключается в том, что о них трудно говорить устно, и практически невозможно использовать Google или любую другую поисковую систему, чтобы найти обсуждения и примеры их использования.
Gigatron
4
@ Андреа Нет, это будет работать, только если типы точно совпадают. Обратите внимание, что я сказал, что наличие неявного значения типа From =:= Toв области видимости подразумевает, что у вас есть неявное преобразование From => To, но импликация не выполняется в обратном направлении; имеющий неявное преобразование A => Bвовсе не означает , у вас есть экземпляр A =:= B. =:=является запечатанным абстрактным классом, определенным в scala.Predef, и имеет только один публично представленный экземпляр, который является неявным и имеет тип A =:= A. Таким образом, вы гарантировано, что неявное значение типа A =:= Bсвидетельствует о том, что Aи Bравны.
Том Крокетт
55

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

Это похоже на два синтаксиса для вызовов методов: «нормальный» с .и ()и операторный синтаксис.

Jesper
источник
2
нуждается в голосовании, потому что makes use of Scala's alternative infix syntax for type operators.полностью упускает это объяснение, без которого все это не имеет смысла
Овидиу
39

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

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

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

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

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

 error: Cannot prove that Null <:< Int
cayhorstmann
источник
2
Я понимаю, что это спустя годы после этого ответа, но я ищу варианты использования <:<, и я думаю, что Orderedпример уже не так убедителен, так как теперь вы предпочитаете использовать Orderingкласс типов, а не Orderedчерту. Что - то вроде: def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second.
ebruchez
1
@ebruchez: пример использования для кодирования типов объединений в неизмененном scala, см. milessabin.com/blog/2011/06/09/scala-union-types-curry-howard
17

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

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

Даниэль С. Собрал
источник
Есть :-)еще один из них? И я бы согласился, что ваш ответ на «Когда я должен их использовать?» относится ко многим вещам.
Майк Миллер
«Они призваны обеспечить способ проверить отношения между классами» <- слишком общий, чтобы быть полезным
Джефф
3
«Что касается вопроса« когда я должен их использовать? », Ответ - не стоит, если только вы не знаете, что должны». <- Вот почему я спрашиваю. Я хотел бы иметь возможность сделать это определение для себя.
Джефф