Это так называемые ограничения обобщенного типа . Они позволяют вам из класса с параметризованным типом или признака дополнительно ограничить один из его параметров типа. Вот пример:
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
- сам аргумент не важен, только зная, что он существует. [править: технически это действительно важно, потому что оно представляет собой неявное преобразование из 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 (аналог ограничения по типу simple <:
)
A <%< B
означает, что A должен быть видимым как B, возможно посредством неявного преобразования (аналогично ограничению простого типа <%
)
Этот фрагмент от @retronym является хорошим объяснением того, как такого рода вещи выполнялись раньше, и как обобщенные ограничения типов облегчают его выполнение.
ДОПОЛНЕНИЕ
Чтобы ответить на ваш дополнительный вопрос, по общему признанию, приведенный мною пример довольно надуманен и явно не полезен. Но представьте, что вы используете его, чтобы определить что-то вроде List.sumInts
метода, который добавляет список целых чисел Вы не хотите, чтобы этот метод вызывался на любом старом List
, просто на List[Int]
. Однако конструктор типа List
не может быть таким ограниченным; вы все еще хотите иметь возможность иметь списки строк, foos, баров и еще много чего. Таким образом, поместив ограничение обобщенного типа на sumInts
, вы можете гарантировать, что только для этого метода имеет дополнительное ограничение, которое может использоваться только на List[Int]
. По сути, вы пишете специальный код для определенных типов списков.