《Learn You a Haskell for Great Good》里面说:

A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes.

Scalaz说:

It provides purely functional data structures to complement those from the Scala standard library. It defines a set of foundational type classes (e.g. Functor, Monad) and corresponding instances for a large number of data structures.

或许我们可以试一试通过学习Haskell,来学习Scalaz。(这一章的内容都是对照Haskell中的Typeclass举例的,推荐没接触Haskell的去看看《Haskell趣学指南》或者《Real World Haskell》。)

配置SBT

这里还是使用sbt来配置获取scalaz。当前的最新的稳定版是7.2.0,

我们可以在sbt中添加以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
val scalazVersion = "7.2.0"
libraryDependencies ++= Seq(
"org.scalaz" %% "scalaz-core" % scalazVersion,
"org.scalaz" %% "scalaz-effect" % scalazVersion,
"org.scalaz" %% "scalaz-typelevel" % scalazVersion,
"org.scalaz" %% "scalaz-scalacheck-binding" % scalazVersion % "test"
)
scalacOptions += "-feature"
initialCommands in console := "import scalaz._, Scalaz._"

然后打开sbt,运行console,来自动获取scalaz

Equal

Eq is used for types that support equality testing. The functions its members implement are == and /=.

Haskell有Eq,Scalaz有Equal。

先保证引入了scalaz,然后测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
scala> 1 === 1
res0: Boolean = true
scala> 1 === "foo"
<console>:14: error: could not find implicit value for parameter F0: scalaz.Equal[Object]
1 === "foo"
^
scala> 1 == "foo"
<console>:14: warning: comparing values of types Int and String using `==' will always yield false
1 == "foo"
^
res2: Boolean = false
scala> 1.some =/= 2.some
res3: Boolean = true
scala> 1 assert_=== 2
java.lang.RuntimeException: 12

Equal使用===,=/=以及assert_===而不是==等符号来判断。值得注意的是除了=/=以外,还有/==来判断,但是优先级不同。

1
2
3
4
5
6
1 =/= 2 && false
1 /== 2 && false
Error:(5, 10) value && is not a member of Int
1 /== 2 && false
^

让我们先到源码看一下,主要相关的也就scalaz.Equalscalaz.std.EqualSyntax这两个文件,其中Equal给出了ADT的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
trait Equal[F] { self =>
////
def equal(a1: F, a2: F): Boolean
///
def contramap[G](f: G => F): Equal[G] = new Equal[G] {
def equal(a1: G, a2: G) = self.equal(f(a1), f(a2))
}
/** @return true, if `equal(f1, f2)` is known to be equivalent to `f1 == f2` */
def equalIsNatural: Boolean = false
// derived functions
// Equal应该满足的Law
trait EqualLaw {
import std.boolean.conditional
def commutative(f1: F, f2: F): Boolean = equal(f1, f2) == equal(f2, f1)
def reflexive(f: F): Boolean = equal(f, f)
def transitive(f1: F, f2: F, f3: F): Boolean = {
conditional(equal(f1, f2) && equal(f2, f3), equal(f1, f3))
}
def naturality(f1: F, f2: F): Boolean = {
conditional(equalIsNatural, equal(f1, f2) == (f1 == f2))
}
}
def equalLaw = new EqualLaw {}
////
val equalSyntax = new scalaz.syntax.EqualSyntax[F] { def F = Equal.this }
}

然后在object Equal中,实现了对于值类型以及引用类型的Equal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
object Equal {
@inline def apply[F](implicit F: Equal[F]): Equal[F] = F
/** Creates an Equal instance based on universal equality, `a1 == a2` */
def equalA[A]: Equal[A] = new Equal[A] {
def equal(a1: A, a2: A): Boolean = a1 == a2
override def equalIsNatural: Boolean = true
}
/** Creates an Equal instance based on reference equality, `a1 eq a2` */
def equalRef[A <: AnyRef]: Equal[A] = new Equal[A] {
def equal(a1: A, a2: A): Boolean = a1 eq a2
}
def equalBy[A, B: Equal](f: A => B): Equal[A] = Equal[B] contramap f
... ...
def equal[A](f: (A, A) => Boolean): Equal[A] = new Equal[A] {
def equal(a1: A, a2: A) = f(a1, a2)
}

最后利用EqualSyntax===等符号加到类型里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package scalaz
package syntax
/** Wraps a value `self` and provides methods related to `Equal` */
final class EqualOps[F] private[syntax](val self: F)(implicit val F: Equal[F]) extends Ops[F] {
final def ===(other: F): Boolean = F.equal(self, other)
final def /==(other: F): Boolean = !F.equal(self, other)
final def =/=(other: F): Boolean = /==(other)
final def (other: F): Boolean = F.equal(self, other)
final def (other: F): Boolean = !F.equal(self, other)
/** Raises an exception unless self === other. */
final def assert_===[B](other: B)(implicit S: Show[F], ev: B <:< F) =
if (/==(other)) sys.error(S.shows(self) + " ≠ " + S.shows(ev(other)))
}
trait ToEqualOps {
implicit def ToEqualOps[F](v: F)(implicit F0: Equal[F]) =
new EqualOps[F](v)
}
trait EqualSyntax[F] {
implicit def ToEqualOps(v: F): EqualOps[F] = new EqualOps[F](v)(EqualSyntax.this.F)
def F: Equal[F]
}

这样在写诸如1 === 2这种代码的时候,首先会通过ToEqualOps中的implicit生成隐式的Equal[Int]然后变成EqualOps[Int]这样就把调用转化成了Equal[Int].equal了。实现语法虽然不如Haskell简洁,但是使用trait XXX来实现ADT,通过XXXops来加入语法的套路也是比较固定。不过这里我们确实没有看到一个实例,都是trait,隐式类型转换到底是通过谁发生的呢?先往下看其他的类型。

Order

Ord is for types that have an ordering. Ord covers all the standard comparing functions such as >, <, >= and <=.

Haskell有Ord,Scalaz有Order。

1
2
3
4
5
6
7
8
9
10
11
12
13
scala> 1 > 2.0
res8: Boolean = false
scala> 1 gt 2.0
<console>:14: error: could not find implicit value for parameter F0: scalaz.Order[Any]
1 gt 2.0
^
scala> 1.0 ?|? 2.0
res10: scalaz.Ordering = LT
scala> 1.0 max 2.0
res11: Double = 2.0

其中?|?返回OrderingGTEQLT。不喜欢用符号的话还可以使用cmp。不过有一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
trait Order[F] extends Equal[F] { self =>
////
def apply(x: F, y: F): Ordering = order(x, y)
def order(x: F, y: F): Ordering
def equal(x: F, y: F): Boolean = order(x, y) == Ordering.EQ
// derived functions
def lessThan(x: F, y: F) = order(x, y) == Ordering.LT
def lessThanOrEqual(x: F, y: F) = order(x, y) != Ordering.GT
... ...
}

可以看到Order是继承Equal的。但是我们依然找不到Order的实现。

Enum

Enum members are sequentially ordered types — they can be enumerated. The main advantage of the Enum typeclass is that we can use its types in list ranges. They also have defined successors and predecessors, which you can get with the succ and pred functions.

Haskell有Enum,Scalaz也是Enum。

1
2
3
4
5
6
7
8
9
10
11
scala> 'a' to 'e'
res30: scala.collection.immutable.NumericRange.Inclusive[Char] = NumericRange(a, b, c, d, e)
scala> 'a' |-> 'e'
res31: List[Char] = List(a, b, c, d, e)
scala> 3 |=> 5
res32: scalaz.EphemeralStream[Int] = scalaz.EphemeralStreamFunctions$$anon$4@6a61c7b6
scala> 'B'.succ
res33: Char = C

Enum里面提供了诸如|=>等方法,利用实现的succpred函数生成EphemeralStream,看到Stream这个名字应该就明白这是一个lazy的数据结构。还提供了诸如fromfromStep,fromStepTo等函数来控制步长。

Enum的trait的定义中,发现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trait Enum[F] extends Order[F] { self =>
def succ(a: F): F
def pred(a: F): F
// derived functions
def succn(n: Int, a: F): F = Enum.succn(n, a)(self)
def predn(n: Int, a: F): F = Enum.predn(n, a)(self)
def min: Option[F] =
None
def max: Option[F] =
None
... ...
}

Enum继承自Order,所以现在的问题就是找一个Enum的实现。

而这些实现都在std.AnyVal里面,比如对于Int

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
implicit val intInstance: Monoid[Int] with Enum[Int] with Show[Int] =
new Monoid[Int] with Enum[Int] with Show[Int] {
override def shows(f: Int) = f.toString
def append(f1: Int, f2: => Int) = f1 + f2
def zero: Int = 0
def order(x: Int, y: Int) = if (x < y) Ordering.LT else if (x == y) Ordering.EQ else Ordering.GT
def succ(b: Int) = b + 1
def pred(b: Int) = b - 1
override def succn(a: Int, b: Int) = b + a
override def predn(a: Int, b: Int) = b - a
override def min = Some(Int.MinValue)
override def max = Some(Int.MaxValue)
override def equalIsNatural: Boolean = true
}

这里实现了Monoid同时mixin了EnumShow,然后所有的Int就都获得了相应的能力了。

Bounded

Bounded members have an upper and a lower bound.

Scalaz在Enum里面提供Bounded的功能,在实现Enum时提供maxmin函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
scala> implicitly[Enum[Char]].min
res43: Option[Char] = Some(?)
scala> implicitly[Enum[Char]].max
res44: Option[Char] = Some( )
scala> implicitly[Enum[Double]].max
res45: Option[Double] = Some(1.7976931348623157E308)
scala> implicitly[Enum[Int]].min
res46: Option[Int] = Some(-2147483648)
scala> implicitly[Enum[(Boolean, Int, Char)]].max
<console>:14: error: could not find implicit value for parameter e: scalaz.Enum[(Boolean, Int, Char)]
implicitly[Enum[(Boolean, Int, Char)]].max

Enum typeclass调用max或者min返回的是Option[T]

Show Read

Members of Show can be presented as strings. Read is sort of the opposite typeclass of Show. The read function takes a string and returns a type which is a member of Read.

Scalaz提供Show但是没有提供Read

1
2
3
4
5
6
7
8
scala> 3.show
res14: scalaz.Cord = 3
scala> 3.shows
res15: String = 3
scala> "hello".println
"hello"

typeclasses 102

用Haskell就是

1
2
3
4
data Person = Person {
name :: String
age :: Int
} deriving (Eq, Show, Read)

换成Scala的话就变成了

1
2
3
4
5
6
7
8
case class Person(name: String, age: Int)
implicit val trafficLightOps: Equal[Person] = Equal.equal(_ == _)
val x = Person("hehe", 21)
val y = Person("hehe", 21)
x === y

a Yes-No typeclass

让我们看看是不是能用Scalaz的风格构造一个自己的typeclass。这里的yesno并没什么实际意义,我们给它命名为truthy,最终的目的是活的1.truthy可以返回true,而实际使用的是CanTruthy[Int].truthys(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
trait CanTruthy[A] { self =>
def truthys(a: A): Boolean
}
object CanTruthy {
def apply[A](implicit ev: CanTruthy[A]): CanTruthy[A] = ev
def truthys[A](f: A => Boolean): CanTruthy[A] = new CanTruthy[A] {
def truthys(a: A): Boolean = f(a)
}
}
trait CanTruthyOps[A] {
def self: A
implicit def F: CanTruthy[A]
final def truthy: Boolean = F.truthys(self)
}
object ToCanIsTruthOps {
implicit def toCanIsTruthyOps[A](v: A)(implicit ev: CanTruthy[A]) =
new CanTruthyOps[A] {
def self = v
override implicit def F: CanTruthy[A] = ev
}
}
import ToCanIsTruthOps._
implicit val intCanTruthy: CanTruthy[Int] = CanTruthy.truthys({
case 0 => false
case _ => true
})

这里实现了CanTruthy[Int],使得可以直接使用1.truthy这种语句。想要扩展到List.

1
2
3
4
implicit def listCanTruthy[A]: CanTruthy[List[A]] = CanTruthy.truthys({
case Nil => false
case _ => true
})

但是如果想使用Nil.truthy的时候就不行了,由于novariance,所以要单独声明一个

1
2
3
4
implicit def listCanTruthy[A]: CanTruthy[List[A]] = CanTruthy.truthys({
case Nil => false
case _ => true
})

然后我们在加入if的语法,注意这里的参数要是pass-by-name的,这样才能保证不使用的时候不计算。

1
2
3
4
5
def truthyIf[A: CanTruthy, B, C](cond: A)(ifyes: => B)(ifno: => C) =
if (cond.truthy) ifyes
else ifno
truthyIf(12){"Yes"}{"No"}

这里 truthy的声明和下面这种写法等价,

1
def truthyIf[A, B, C](cond: A)(ifyes: => B)(ifno: => C)(implicit ev: CanTruthy)

然后在cond.truthy的时候,发生了隐式类型转换。




X