这个是为了学习Scalaz而翻译的笔记,Scalaz是为Scala提供函数式编程能力的库,实现了诸如FunctorMonadtype classes,恩,你在Haskell熟悉的东西,想在scala里面用基本都要去Scalaz里面找。

什么是polymorphism

polymorphism一般翻译成多态,虽然我们在C++里面也会提多态,但是在programming languages and type theory里面就不太一样。简单来说,一个polymorphic type 就是指其操作可以应用到多个其他类型上。基础的多态有三种:

  • Parametric polymorphism。这个就是像常见的泛型一样,类型定义包含一个或多个类型变量,这样就可以通过给定具体的类型参数,来接受不同类型的值了。比如:
1
2
3
4
def head[A](xs: List[A]) = xs(0)
head(1::2::Nil)
case class Car(make: String)
head(Car("civic")::Car("CR-V")::Nil)
  • Subtype polymorphism。也叫subtyping或者inclusion polymorphism。比如C++里面基于虚函数的多态,一个名字(引用?形参?)代表的实例都有共同的superclass
1
2
3
4
5
trait Plus[A] {
def plus(a2: A): A
}
def plus[A <: Plus[A]](a1: A, a2: A): A = a1.plus(a2)
  • Ad-hoc polymorphism。这个比如C++的函数重载。即一个函数通过有限的指定的类型或者其组合来区分不同的实现。而在scala里面,还可以通过隐式类型转换实现。其实这里可以看出来Scala隐式类型转换的一个好处,我们可以针对已有的类型添加功能,而不用更改其代码,并且可以通过作用域来控制其起作用的时机。
1
2
3
4
5
6
7
8
9
10
trait Plus[A] {
def plus(a1: A, a2: A): A
}
def plus[A: Plus](a1: A, a2: A): A =
implicitly[Plus[A]].plus(a1, a2)
implicit val StringPlus = new Plus[String] {
def plus(a1: String, a2: String) = a1 + a2
}

一个ad-hoc polymorphism的例子

一般sum是这么写的

1
2
3
def sum(xs: List[Int]): Int = xs.foldLeft(0) {_ + _}
sum(List(1,2,3,4))

如果我们想把它弄得更generalize一点,提供一种抽象,Monoid,(你懂得就是那个Monoid),它应该满足包含mapzero,两个函数,以及一些性质,这个可以通过很多haskell的书看到。

1
2
3
4
object IntMonoid {
def mappend(a: Int, b: Int): Int = a + b
def mzero: Int = 0
}

然后把上面那个改写成

1
2
def sum(xs: List[Int]): Int =
xs.foldLeft(IntMonoid.mzero)(IntMonoid.mappend)

然后在进一步,抽象出Monoid

1
2
3
4
5
6
7
8
9
10
11
12
trait Monoid[A] {
def mappend(a1: A, a2: A):A
def mzero: A
}
object IntMonoid extends Monoid[Int] {
def mappend(a: Int, b: Int): Int = a + b
def mzero: Int = 0
}
def sum(xs: List[Int], m: Monoid[Int]): Int =
xs.foldLeft(m.mzero)(m.mappend)

但是这样sum还是没有摆脱具体的类型,

1
2
3
4
def sum[A](xs: List[A], m: Monoid[A]): A =
xs.foldLeft(m.mzero)(m.mappend)
sum(List(1,2,3,4), IntMonoid)

这样显示的给出Monoid未免太过复杂,这个时候就需要implicit出马了

1
2
3
4
5
6
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
xs.foldLeft(m.mzero)(m.mappend)
implicit val intMonoid = IntMonoid
sum(List(1,2,3,4))

这样对于任何一个已有的类型,我们都可以不用更改其自身通过implicit来完成像是给它添加的新功能的效果,而且是编译时的。

FoldLeft

下面把目光转向foldleft操作。

1
2
3
4
object FoldLeftList {
def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) =
xs.foldLeft(b)(f)
}

然后

1
2
def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
FoldLeftList.foldLeft(xs, m.mzero, m.mappend)

这样显然长得不好看,所以,我们采用之前的那种方式抽象出一个typeclass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
trait FoldLeft[F[_]] {
def foldLeft[A, B](xs: F[A], b: B, f: (B, A) => B): B
}
object FoldLeft {
implicit val FoldLeftList: FoldLeft[List] = new FoldLeft[List] {
def foldLeft[A, B](xs: List[A], b: B, f: (B, A) => B) = xs.foldLeft(b)(f)
}
}
def sum[M[_]: FoldLeft, A: Monoid](xs: M[A]): A = {
val m = implicitly[Monoid[A]]
val fl = implicitly[FoldLeft[M]]
fl.foldLeft(xs, m.mzero, m.mappend)
}
def sum1[M[_], A](xs: M[A])(implicit m: Monoid[A], f: FoldLeft[M]) =
f.foldLeft(xs, m.mzero, m.mappend)

这里使用了另外一种语法,用implicitly代替了implicit的参数,看起来更加的明确一点,函数接受一个可以在Monoid上可以foldLeft的类型,然后在implicit view bound里面获取实际的foldLeft,以及Monoid,然后,执行。这里面诸如MonoidFoldLeft在Haskell称为Typeclass。而在Scala中,Scalaz提供了大量的typeclass,

method injection

虽然像之前那样能完成目标,但是这样子,如果我们想单独使用一个加法的时候,

1
2
3
4
def plus[A: Monoid](a: A, b: A): A = implicity[Monoid[A]].mappend(a, b)
//使用的时候
plus("1","2")

这样写起来很麻烦,明明是类型本身的操作,这样写感觉不好看。

为了elegant!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait MonoidOp[A] {
val F: Monoid[A]
val value: A
def |+|(a2: A) = F.mappend(value, a2)
}
implicit def toMonoidOp[A: Monoid](a: A): MonoidOp[A] = new MonoidOp[A] {
val F = implicitly[Monoid[A]]
val value = a
}
1 |+| 2
"1" |+| "2"

这样就把新方法注入到已有的类型中了,而且,想要加新的类型就去实现Monoid就有了。

这部分是 Learning from Scalaz的第0部分。也是为什么要用typeclass,要有方便的高阶类型的操作,为什么需要ScalaZ。




X