Scala中sum类型的函数Map为何无法声明为Fruit=>String类型
核心原因:Scala的函数型变规则
首先给出问题对应的示例代码:
sealed trait Fruit case class Apple(color: String) extends Fruit case class Orange(color: String) extends Fruit def getAppleColor(apple: Apple) = apple.color def getOrangeColor(orange: Orange) = orange.color val myMap: Map[String, Fruit] = Map( "myApple" -> Apple("red"), "myOrange" -> Orange("orange"), ) val myMapOfFunctions: Map[String, Apple with Orange => String] = Map( "myAppleColorFun" -> getAppleColor, "myOrangeColorFun" -> getOrangeColor, )
普通值Map能正常声明的原因
Scala标准库中Map的类型声明为Map[K, +V],其中值类型V是**协变(covariant)**的:
- 如果类型
B是类型A的子类型,那么Map[K, B]就是Map[K, A]的子类型,可以合法赋值给声明为Map[K, A]的变量。 - 示例中
Apple、Orange都是Fruit的子类型,因此存储这两个实例的Map自然可以声明为Map[String, Fruit],完全符合类型安全要求。
函数Map不能声明为Map[String, Fruit => String]的原因
Scala中一元函数类型T => R本质是特质Function1[-T, +R]的语法糖,它的型变规则和普通泛型类有核心差异:
- 返回值类型
R是协变的,和普通泛型的协变逻辑一致; - **参数类型
T是逆变(contravariant)**的:如果类型B是类型A的子类型,那么A => R反而是B => R的子类型,继承关系的方向完全反转。
这个规则是类型安全的强制要求:如果一个变量被声明为Fruit => String类型,意味着调用方可以给它传入任意Fruit子类的实例(不管是Apple还是Orange),函数必须能正常处理并返回String。
反例验证:如果允许把仅接受Apple参数的
getAppleColor赋值给Fruit => String类型的变量,调用方完全可以给这个变量传入Orange实例,运行时会直接抛出类型转换错误,彻底破坏类型安全,因此编译器会直接禁止这种操作。
回到示例本身:
getAppleColor类型是Apple => String,getOrangeColor是Orange => String,二者都不是Fruit => String的子类型,自然无法放入声明为Map[String, Fruit => String]的Map中。- 示例中
Apple with Orange => String能通过编译,是因为Apple with Orange是同时继承了Apple和Orange的交叉类型,它既是Apple的子类型,也是Orange的子类型。根据逆变规则,Apple => String、Orange => String都是(Apple with Orange) => String的子类型,因此可以合法放入Map。但这个类型没有实际使用价值——你永远构造不出一个同时是Apple又是Orange的实例,两个case class不存在公共实现子类。
内容的提问来源于stack exchange,提问作者erprincipe
相关产品推荐
相关产品推荐

