Smilingleo's Blog

Function的协、逆变

February 04, 2016

泛型编程的时候,协变(covariant)还是逆变(contravariant)很重要,在设计上层API接口的时候,正确的使用协变、逆变可以更好地约束程序员的行为,让实现变得安全、一致。

协变、逆变在一般时候是比较容易理解的,但是来到FP世界之后,Function的协变、逆变就变得比较复杂。

比如: 对于trait List[+T]

class Animal
class Dog
class "List[Animal]" as LA
class "List[Dog]" as LD

Animal <|-- Dog
LA <|-- LD

那对于trait CList[-T]

class Animal
class Dog
class "CList[Animal]" as LA
class "CList[Dog]" as LD

Animal <|-- Dog
LA --|> LD

这些还都容易理解,对于trait Func[-I, +O]的理解就比较费劲了。

import scala.reflect.runtime.universe._

class Animal
case class Dog(name: String) extends Animal

class Material
case class Wood(color: String) extends Material

trait Func[-I, +O] {
  def apply(input: I): O
}

typeOf[Dog] <:< typeOf[Animal]  // return true
typeOf[Func[Material, Dog]] <:< typeOf[Func[Wood, Animal]]    // retrun true
class Animal
class Dog
class Wood
class Material

class "Func[Wood, Animal]" as LA
class "Func[Material, Dog]" as LD


Wood --|> Material
LA <|-- LD
Animal <|-- Dog

理解这个的关键是理解“里氏替换原则”,也就是,任何父类出现的地方,如果用其子类来替换都应该是安全的。从这个角度看,这个Func完成的工作是用某种材料来制作某种动物,Func[Wood, Animal]是输入木头制作任何动物,Func[Material, Dog]是输入任何材料来制作狗。考虑下面的应用场景:

val woods: List[Wood] = ...         //给定一堆木头

val makeAnimalWithWood: Func[Wood, Animal] = ...
val makeDogWithMaterial: Func[Material, Dog] = ...

val describer: Animal -> String = ...

woods.map(makeAnimalWithWood)       // return List[Animal]
     .map(describer)                // 接受Animal返回String

根据里氏替换原则,用makeDogWithMaterial替换makeAnimalWithWood是安全的。反过来,看下面代码:

val materials: List[Material] = ...           // 给定一堆材料

val makeAnimalWithWood: Func[Wood, Animal] = ...
val makeDogWithMaterial: Func[Material, Dog] = ...

val describer: Dog -> String = ...

materials.map(makeDogWithMaterial)       // return List[Dog]
     .map(describer)                // 接受Dog返回String

这时候,用makeAnimalWithWood来替换makeDogWithMaterial就不行了,因为materials.map(makeAnimalWithWood)就会编译错误了,因为makeAnimalWithWood只接受Wood,而代码传递过来的是Material.


Prev: JSON解析利器---JQ

Next: 用Mixin组合实现Scala中的AOP


Blog comments powered by Disqus.

© Wei Liu | 2024