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

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

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

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

 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 AString . Вы можете думать об этом как о доказательстве того, что A – это String – сам аргумент не важен, только зная, что он существует. [edit: хорошо, технически это на самом деле важно, потому что оно представляет собой неявное преобразование из A в String , что позволяет вам называть a.length и не иметь крик компилятора у вас]

Теперь я могу использовать его так:

 scala> Foo("blah").getStringLength res6: Int = 4 

Но если я попробовал использовать его с Foo содержащим нечто, отличное от String :

 scala> Foo(123).getStringLength :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, возможно через неявное преобразование (аналогично простому типу <% )

Этот fragment by @retronym - хорошее объяснение того, как это делалось раньше, и как обобщенные ограничения типов облегчают сейчас.

ДОПОЛНЕНИЕ

Чтобы ответить на ваш последующий вопрос, правда, пример, который я дал, довольно надуманный и явно не полезный. Но представьте, как использовать его, чтобы определить что-то вроде метода List.sumInts , который добавляет список целых чисел. Вы не хотите, чтобы этот метод вызывался в любом старом List , просто List[Int] . Однако конструктор типа List не может быть так ограничен; вы все равно хотите иметь списки строк, foos, bars и whatnots. Поэтому, поставив ограничение обобщенного типа на sumInts , вы можете убедиться, что только этот метод имеет дополнительное ограничение, которое можно использовать только в List[Int] . По сути, вы пишете специальный код для определенных видов списков.

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

 def getStringLength(implicit evidence: A =:= String) 

использует альтернативный синтаксис синтаксиса Scala для операторов типа .

Итак, A =:= String совпадает с =:=[A, String]=:= это просто class или черта с причудливым именем). Обратите внимание, что этот синтаксис также работает с «обычными» classами, например, вы можете написать:

 val a: Tuple2[Int, String] = (1, "one") 

как это:

 val a: Int Tuple2 String = (1, "one") 

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

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

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

 class Pair[T](val first: T, val second: T) 

Теперь вы хотите добавить метод smaller , например:

 def smaller = if (first < second) first else second 

Это работает только в том случае, если T упорядочен. Вы можете ограничить весь class:

 class Pair[T <: Ordered[T]](val first: T, val second: T) 

Но это кажется позором - для classа можно использовать, когда T не упорядочен. С ограничением типа вы все равно можете определить smaller метод:

 def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second 

Это нормально, чтобы создать, скажем, Pair[File] , если вы не называете ее smaller .

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

  error: Cannot prove that Null <:< Int 

Это зависит от того, где они используются. Чаще всего при использовании при объявлении типов неявных параметров они являются classами. Они также могут быть объектами в редких случаях. Наконец, они могут быть операторами объектов Manifest . Они определены внутри scala.Predef в первых двух случаях, хотя и не особенно хорошо документированы.

Они призваны обеспечить способ проверить взаимосвязь между classами, как и <: и <% do, в ситуациях, когда последнее не может быть использовано.

Что касается вопроса «когда я должен их использовать?», Ответ не должен, если вы не знаете, что вам следует. :-) EDIT : Хорошо, хорошо, вот несколько примеров из библиотеки. На Either вас есть:

 /** * Joins an Either through Right. */ 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 Either through Left. */ 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 

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

Давайте будем гением компьютера.