Scala(或者functional programming)中实现polymorphism的正确模式.
Type-safety is making use of what we know of our values at compile-time to minimize the consequences of most mistakes.
他们在数学上对应cartesian product和disjoint union, 通俗的讲就是"ands"和"ors", scala中用case class和sealed trait分别表示两者. 举个例子:
- a shape is a rectangle or acircle
- a rectangle has a width and a height
- a circle has a radius
sealed trait Shape
final case class Rectangle(width: Double, height: Double) extends Shape
final case class Circle(radius: Double) extends Shape
val rect: Shape = Rectangle(3.0, 4.0)
val circ: Shape = Circle(1.0)
- Run-time type casting everywhere
- Data与polymorphism method耦合 -- 两者往往在同一个class里面, ie, 同一个type (对于scala和java来说type与class是1对1的关系)
基于上面三点, 在fp风格的scala中, 应该完全抛弃inheritance polymorphism, 使用parametric polymorphism和ad-hoc polymorphism代替. 后面两者的结合就是typeclass.
Typeclass是fp实现abstraction和polymorphism最基础的脚手架.
下面这段inheritance polymorphism的代码应该被废弃:
trait Pet {
def sleep(hours: Int): String
def eat(something: String): String
}
class Cat(val nickname: String) extends Pet {
override def sleep(hours: Int) = s"cat $nickname sleeps for $hours h"
override def eat(something: String) = s"cat $nickname eats $something"
}
def petEatThanSleep(p: Pet): String = p.eat + "than" + p.sleep
使用ADTs+typeclass取而代之:
// ADTs封装data(or states)
sealed trait Pet
case class Cat(nickname: String)
// type class实现逻辑上的polymorphism
trait PetBehave[P] {
def sleep(hours: Int): String
def eat(something: String): String
}
object PetBehave {
// instance here, no inheritence
implicit val catBehave = new PetBehave[Cat] {
override def sleep(hours: Int) = s"cat $nickname sleeps for $hours h"
override def eat(something: String) = s"cat $nickname eats $something"
}
}
// no run-time type casting
// magic of the scala implicit parameter
def petEatThanSleep[T: PetBehave](hours: Int, something: String): String = {
val evidence = implicitly[PetBehave[T]]
evidence.eat(something) + "than" + evidence.sleep(hours)
}
To be continued Orz...