Що значить <: <, <% <, і =: = означають у Scala 2.8 та де вони задокументовані?


201

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

Подальше запитання: коли я повинен використовувати ці прикольні символи / класи, і чому?


6
Ось споріднений питання , який може відповісти на ваше запитання , по крайней мере , частково: stackoverflow.com/questions/2603003/operator-in-scala
Ярдена

13
symbolhound.com ваш код пошуку друг :)
рон

Чи typeclassвиконують завдання Haskell ці оператори? Приклад compare :: Ord a => a -> a -> Ordering:? Я намагаюся зрозуміти цю концепцію Scala стосовно її частини Хаскелл.
Кевін Мередіт

Відповіді:


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подається компілятором, iff Ais String. Ви можете вважати це доказом, що Aце String- сам аргумент не важливий, лише знаючи, що він існує. [редагувати: ну, технічно це насправді важливо, оскільки це являє собою неявну конверсію з 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на a Foo[String]. Це обмеження виконується під час компіляції, що класно!

<:<і <%<працювати аналогічно, але з незначними варіаціями:

  • A =:= B означає, що A повинен бути саме B
  • A <:< Bозначає, що A повинен бути підтипом B (аналогічно обмеженню простого типу <:)
  • A <%< Bозначає, що A має бути видимим як B, можливо, шляхом неявного перетворення (аналогічного простому обмеженню типу <%)

Цей фрагмент від @retronym є хорошим поясненням того, як подібні речі використовувались і як узагальнені обмеження типу спрощують його зараз.

ДОБАВЛЕННЯ

Щоб відповісти на ваше подальше запитання, правда, приклад, який я наводив, досить надуманий і не очевидно корисний. Але уявіть, як використовувати його для визначення чогось типу List.sumIntsметоду, який додає список цілих чисел. Ви не хочете, щоб цей метод використовувався на будь-якому старому List, лише на List[Int]. Однак Listконструктор типів не може бути таким обмеженим; ви все ще хочете мати змогу мати списки струн, футів, смуг та ін. Таким чином, розміщуючи узагальнене обмеження типу sumInts, ви можете переконатися, що саме цей метод має додаткове обмеження, яке може бути використане лише на a List[Int]. По суті, ви пишете спеціальний код для певних типів списків.


3
Ну добре, але є також методи з тими самими назвами, про Manifestякі ви не згадували.
Даніель К. Собрал

3
Методи на Manifestє <:<і >:>тільки ... оскільки ОП згадав саме про 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
@Andrea Nope, це спрацює лише в тому випадку, якщо типи точно рівні. Зауважте, що я сказав, що маючи неявне значення типу 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")

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


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

2
Я усвідомлюю, що це роки через цю відповідь, але я шукаю випадки використання <:<, і я вважаю, що Orderedприклад вже не є таким переконливим, оскільки зараз ви скоріше використовуєте Orderingклас, а не Orderedознаку. Що - щось на кшталт: def smaller(implicit ord: Ordering[T]) = if (ord.lt(first, second)) first else second.
ebruchez

1
@ebruchez: випадок використання для кодування типів союзу в немодифікованому масштабі

17

Це залежить від того, де вони використовуються. Найчастіше при використанні під час декларування типів неявних параметрів це класи. Вони можуть бути об'єктами і в рідкісних випадках. Нарешті, вони можуть бути операторами на Manifestоб’єктах. Вони визначені всередині scala.Predefперших двох випадків, хоча вони не особливо добре зафіксовані.

Вони покликані забезпечити спосіб перевірити взаємозв'язок між класами, як <:і <%робити, в ситуаціях, коли останні не можуть бути використані.

Що стосується питання "коли я повинен їх використовувати?", То відповіді ви не повинні, якщо ви не знаєте, що слід. :-) 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

Ви знайдете деякі інші приклади в колекціях.


Є :-)ще одна з них? І я погодився б, що ваша відповідь на запитання "Коли я повинен їх використовувати?" стосується дуже багатьох речей.
Майк Міллер

"Вони мають на меті запропонувати спосіб перевірити взаємозв'язок між класами" <- занадто загальний, щоб бути корисним
Джефф

3
"Що стосується питання" коли я повинен їх використовувати? ", Відповідь не слід, якщо ви не знаєте, що слід". <- Ось чому я прошу. Я хотів би, щоб я міг визначити це для себе.
Джефф
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.