Skip to content

Instantly share code, notes, and snippets.

@SabaPing
Last active October 5, 2018 10:12
Show Gist options
  • Save SabaPing/5825cb43b41a14efafdc2d48587da79f to your computer and use it in GitHub Desktop.
Save SabaPing/5825cb43b41a14efafdc2d48587da79f to your computer and use it in GitHub Desktop.
Polymorphism in Scala

Polymorphism in Scala

Scala(或者functional programming)中实现polymorphism的正确模式.

TL;DR

1. 相信在Coding时, 花额外的精力保证type-safety, 最终能够提升工程质量

Type-safety is making use of what we know of our values at compile-time to minimize the consequences of most mistakes.

2. Functional programming(fp)中有两种封装data的基本形式, 分别是product和coproduct, 合在一起叫algebraic data types(ADTs)

他们在数学上对应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)

3. Inheritance polymorphism不但破坏了type-safety, 还阻碍了更高级别的abstraction

  • 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...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment