0

考虑以下示例

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 对象时,会将一个县对象列表传递给它,并且编译器不会抛出错误,因为它会将其识别CountryLookup

但是在下一行中,当我尝试调用以 Country 作为参数类型(而不是预期的 Lookup)的方法时,我得到一个Type mismatch错误。为什么会这样?

4

1 回答 1

3

countryValue是一个从Countryto的函数String(实际上是一个将 eta 扩展为函数的方法,但现在不相关),即 a Function1[Country, String].

render期望一个Function1[Lookup, String].

所以我们要回答的问题是

GivenCountryLookup

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]

换句话说,当一个函数承诺不少(协变返回类型)并且它不需要更多(逆变输入类型)时,它就是另一个函数的子类型。

例如:只要需要接受 a 并Animal返回aApple的函数,就可以使用接受 aDog并返回 a的函数Fruit。更正式的

Dog <: Animal
Fruit <: Apple
Function1[Animal, Apple] <: Function1[Dog, Fruit]

但相反的情况并非如此:如果您的函数处理狗并返回任何水果,那么它肯定不能用于处理任何动物并返回苹果。

于 2015-09-16T10:43:18.910 回答