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

171

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

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

  • 6
    Вот связанный вопрос, который может хотя бы частично ответить на ваш вопрос: stackoverflow.com/questions/2603003/operator-in-scala
  • 12
    symbolhound.com ваш друг для поиска кода :)
Показать ещё 1 комментарий
Теги:
scala-2.8

4 ответа

189
Лучший ответ

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

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

  • 3
    Хорошо, хорошо, но есть также методы с такими же именами в Manifest , о которых вы не упомянули.
  • 3
    Методы в Manifest доступны только для <:< и >:> ... поскольку OP упомянул ровно 3 разновидности обобщенных ограничений типов, я предполагаю, что это то, что его интересовало.
Показать ещё 15 комментариев
48

Не полный ответ (другие уже ответили на это), я просто хотел бы отметить следующее, что, возможно, поможет лучше понять синтаксис: как обычно вы используете эти "операторы", например, в примере с пелотомом:

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")

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

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

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

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

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

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

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

  • 0
    Является ли :-) еще один из них? И я бы согласился, что ваш ответ на «Когда я должен их использовать?» относится ко многим вещам.
  • 0
    «Они предназначены для проверки отношений между классами» <- слишком общий, чтобы быть полезным
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню