Путаница с параметризацией типа скалы
Рассмотрим следующий пример
abstract class Lookup(val code:String,val description:String)
class USState(code:String, description:String, val area:Symbol)
extends Lookup(code,description)
class Country(code:String, description:String, val postCode:String)
extends Lookup(code,description)
class HtmlLookupSelect(data:List[Lookup]) {
def render( valueMaker:(Lookup) => String ) =
data.map( (l) => valueMaker(l) )
}
val countries = List(
new Country("US","United States","USA"),
new Country("UK","Unites Kingdom","UK"),
new Country("CA","Canada","CAN"))
def lookupValue(l:Lookup) = l.description
def countryValue(c:Country) = c.description + "(" + c.postCode + ")"
val selector = new HtmlLookupSelect(countries) //Doesn't throw an error
selector.render(countryValue) //Throws an error
HtmlLookupSelect
ожидает список объектов Lookup в качестве параметра конструктора. При создании объекта HtmlLookupSelect ему передается список объектов округа, и компилятор не выдает ошибку, поскольку он распознает Country
как подкласс Lookup
Но в следующей строке, когда я пытаюсь вызвать метод с Country в качестве типа параметра (вместо ожидаемого Lookup), я получаю Type mismatch
ошибка. Почему это происходит?
1 ответ
countryValue
это функция от Country
в String
(на самом деле это метод, который становится расширенным в функцию, но не актуален сейчас), т.е. Function1[Country, String]
,
render
ожидает Function1[Lookup, String]
,
Итак, вопрос, на который мы хотим ответить
Дано
Country
это подтипLookup
является
Function1[Country, String]
подтипFunction1[Lookup, String]
?
Чтобы ответить на это, давайте посмотрим на определение Function1
:
trait Function1[-T1, +R] extends AnyRef
Увидеть -T1
? Это входной параметр и -
Значит это Function1
является контравариантным по своему входному параметру и ковариантным по своему выходному параметру.
Так что если A <: B
(где <:
это отношение подтипа) и R <: S
затем
Function1[B, R] <: Function[A, S]
В вашем примере Country <: Lookup
так
Function1[Country, String] </: Function[Lookup, String]
Другими словами, функция является подтипом другой, когда она обещает не меньше (ко-вариантный тип возврата) и больше не требует (противоположно-вариантный тип ввода).
Например: функция, которая принимает Animal
и возвращает Apple
может быть использован всякий раз, когда функция, которая принимает Dog
и возвращает Fruit
необходимо. Более формально
Dog <: Animal
Fruit <: Apple
Function1[Animal, Apple] <: Function1[Dog, Fruit]
но обратное неверно: если ваша функция работает с собаками и возвращает какие-либо фрукты, она, безусловно, не может использоваться для обработки любого животного и возвращения яблока.