上一次,我们见到了kindtype,以及Tagged type,然后大概看了Semigroup以及Monoid这两种抽象了二元运算的类型。之前在讨论的时候,就已经说过,FunctorMonoid等应该满足一些性质,称之为Law

Functor Laws

All function are expected to exhibit certain kinds of functor-like properties and behaviors. … The first functor law states that if we get back should be the same as the original functor.

换而言之,就是任何Functor``map``identity以后应该不变。

1
List(1,2,3) map {identity} assent_=== List(1,2,3)

The second law says that composing two functions and then mapping the resulting function over a functor should be the same as first mapping one function over the functor and then mapping the other one.

这个就是结合律,

1
2
(List(1,2,3) map {{(_:Int) * 3} map {(_: Int) + 1}}) assert_=== 
(List(1,2,3) map {(_:Int) * 3} map {(_: Int) + 1})

为了保证这些性质可以被编译器检查,而不仅仅是人工手动验证,scalazFunctor中提供了FunctorLaw

1
2
3
4
5
6
7
8
9
10
11
12
trait FunctorLaw extends InvariantFunctorLaw {
/** The identity function, lifted, is a no-op. */
def identity[A](fa: F[A])(implicit FA: Equal[F[A]]): Boolean =
FA.equal(map(fa)(x => x), fa)

/**
* A series of maps may be freely rewritten as a single map on a
* composed function.
*/

def composite[A, B, C](fa: F[A], f1: A => B, f2: B => C)(implicit FC: Equal[F[C]]): Boolean =
FC.equal(map(map(fa)(f1))(f2), map(fa)(f2 compose f1))
}

然后通过使用scalacheckbinding,可以直接检查是不是满足性质而不用自己实现额外的代码。

Applicative Laws

Applicative也有相应的Laws

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
trait ApplicativeLaw extends ApplyLaw {
/** `point(identity)` is a no-op. */
def identityAp[A](fa: F[A])(implicit FA: Equal[F[A]]): Boolean =
FA.equal(ap(fa)(point((a: A) => a)), fa)

/** `point` distributes over function applications. */
def homomorphism[A, B](ab: A => B, a: A)(implicit FB: Equal[F[B]]): Boolean =
FB.equal(ap(point(a))(point(ab)), point(ab(a)))

/** `point` is a left and right identity, F-wise. */
def interchange[A, B](f: F[A => B], a: A)(implicit FB: Equal[F[B]]): Boolean =
FB.equal(ap(point(a))(f), ap(f)(point((f: A => B) => f(a))))

/** `map` is like the one derived from `point` and `ap`. */
def mapLikeDerived[A, B](f: A => B, fa: F[A])(implicit FB: Equal[F[B]]): Boolean =
FB.equal(map(fa)(f), ap(fa)(point(f)))
}

Semigroup Laws

Semigroup Laws比较简单:

  • closure,对于任意a,b属于semigroup Fappend(a,b)也必须属于F,这点由类型系统来保证,所以scalaz中没有这个。

  • associativity,对于任意a,b,c属于semigroup Fappend(append(a,b),c)应该与append(a, append(b,c))相等。

1
2
3
4
trait SemigroupLaw {
def associative(f1: F, f2: F, f3: F)(implicit F: Equal[F]): Boolean =
F.equal(append(f1, append(f2, f3)), append(append(f1, f2), f3))
}

Monoid Laws

由于Monoid本身就是Semigroup,所以他应该满足Semigroup Law。此外,还要存在zero。

1
2
3
4
5
6
trait MonoidLaw extends SemigroupLaw {
def leftIdentity(a: F)(implicit F: Equal[F]) =
F.equal(a, append(zero, a))
def rightIdentity(a: F)(implicit F: Equal[F]) =
F.equal(a, append(a, zero))
}

Option as Monoid

One way is to treat Maybe a as a monoid only if its type parameter a is a monoid as well and then implement mappend in such a way that it uses the mappend operation of the values that are wrapped with Just.

我们来看看scalaz里面是怎么对待option as monoid的。

1
2
3
4
5
6
7
8
9
10
11
12
implicit def optionMonoid[A: Semigroup]: Monoid[Option[A]] = 
new Monoid[Option[A]] {
def append(f1: Option[A], f2: => Option[A]) =
(f1, f2) match {
case (Some(a1), Some(a2)) => Some(Semigroup[A].append(a1, a2))
case (Some(a1), None) => f1
case (None, sa2 @ Some(a2)) => sa2
case (None, None) => None
}

def zero: Option[A] = None
}

当我们并不能保证Option的内容是Monoid的时候,可以直接使用First,忽略掉第二个值。也可以使用Last,忽略掉前面的值。

1
2
3
4
Tags.First('a'.some) |+| Tags.First('b'.some)
// res0: scalaz.@@[Option[Char],scalaz.Tags.First] = Some(a)
Tags.Last('a'.some) |+| Tags.Last('b'.some)
// res1: scalaz.@@[Option[Char],scalaz.Tags.Last] = Some(b)

Foldable

Because there are so many data structures that work nicely with folds, the Foldable type class was introduced. Much like Functor is for things that can be mapped over, Foldable is for things that can be folded up!

scalaz中的Foldable,提供这种功能。

1
2
3
4
5
6
7
8
9
10
trait Foldable[F[_]]  { self =>
////
import collection.generic.CanBuildFrom

/** Map each element of the structure to a [[scalaz.Monoid]], and combine the results. */
def foldMap[A,B](fa: F[A])(f: A => B)(implicit F: Monoid[B]): B
/**Right-associative fold of a structure. */
def foldRight[A, B](fa: F[A], z: => B)(f: (A, => B) => B): B
...
}

然后,在基本操作之上,FoldableOps里面注入了大量的操作函数。

1
2
3
4
5
List(1,2,3) foldMap {(_: Int) + 1}
// res0: Int = 9

List(true, false, true, true) foldMap {Tags.Disjunction.apply}
// res1: scalaz.@@[Boolean,scalaz.Tags.Disjunction] = true

更多操作可以直接看这里




X