1. monad单子(monads)
定义:monad是一个满足associativity和identity法则的monadic组合的最小集合的实现,是对象类型的包装,用一个对象包装另一个对象,monad单子既不是trait特质也不是class类,大多数集合类型是单子,但反之不成立
补充:
- Functor函子——类别之间的映射,换言之,是高阶类型之间的映射,高阶类型是以类型构造器为参数的类型,举个例子,Int、List[Int]、List[T]是一阶类型,List[C[Int]]、List[C[T]]是高阶类型
- Monad单子——最小操作及其组合子的集合(单子常见的运算组合,有三种形式:1 unit + flatMap; 2 unit + compose; 3 unit + map + join)
1.1 问题点引出:map函数的泛化
// map存在于多种类型中的不同参数模板
def map[A,B](ga:Gen[A])(f: A=>B):Gen[B]
def map[A,B](pa:Parser[A])(f: A=>B):Parser[B]
def map[A,B](oa:Option[A])(f: A=>B):Option[B]
// map函数的泛化
trait Functor[F[_]]{ // 使用类型构造器F[_]对map函数进行泛化,支持Gen、Parser、Option等类型
def map[A,B](fa:F[A])(f: A=>B):F[B]
}
// 通过指定类型构造器F,创建适配List的map函数
// 对trait进行实例化时,需要使用具体类型List替换F[_],使用具体逻辑填充map等号右侧
val listFunctor = new Functor[List]{
def map[A,B](as: List[A])(f: A => B):List[B] = as.map(f)
}
listFunctor.map(List(1,2,3))
// 改进Functor,通过类型构造器,完成分派distribute,增加unzip功能
trati Functor[F[_]]{
def map[A,B](fa: F[A])(f: A=>B):F[B]
// 这里的distribute是带有具体实现的方法,实例化Functor时可以不对distribute进行overriding
def distribute[A,B](fab: F[(A,B)]):(F[A],F[B]) = (map(fab)(_._1), map(fab)(_._2))
def codistribute[A,B](e: Either[F[A], F[B]]): F[Either[A, B]] = e match {
case Left(fa) => map(fa)(Left(_))
case Right(fb) => map(fb)(Right(_))
}
}
// 对trait进行实例化,distribute函数可以使用默认实现
val listUnzipFunctor = new Functor[List]{
def map[A,B](fa:F[A])(f: A=>B):F[B] = fa.map(f)
}
listUnzipFunctor.map(List(1,2,3))
listUnzipFunctor.distribute(List((1,"1"),(2,"2"),(3,"3")))
1.2 函子法则:
无论合适创建Functor,既要考虑实现哪些抽象方法,也要考虑遵循哪些法则,scala不会强加任何法则
- 可能存在翻译问题,函子法则其实是指抽象过程中应当注意到的类型约束,并学会用==去时刻注意是否满足这些类型约束条件,防止出现编译正确但计算错误的情形
- 法则帮助接口定义了语法,方便代数定义在实例间进行独立地推演,例如,Monoid[A]和Monoid[B]构建Monoid[(A,B)]也是满足结合律的
- 法则帮助抽象接口中的函数编写各种组合子,例如,map(x)(a => a) == x
1.3 问题的引出:map2函数的泛化
def map2[A,B,C](fa: Gen[A] , fb: Gen[B] )(f: (A,B) => C): Gen[C] = fa.flatMap(a => fb.map(b => f(a,b)))
def map2[A,B,C](fa: Parser[A], fb: Parser[B])(f: (A,B) => C): Parser[C] = fa.flatMap(a => fb.map(b => f(a,b)))
def map2[A,B,C](fa: Option[A], fb: Option[B])(f: (A,B) => C): Option[C] = fa.flatMap(a => fb.map(b => f(a,b)))
1.4 使用Monad统一Gen、Parser、Option数据类型
通过将上述各种map2抽取到一个trait里,避免重复定义,定义一个最小的原始操作集合,通过组合定义更多可用的操作
trait Mon[F[_]]{
// 无法编译,因为用到了fa.flatMap和fb.map,上下文中无法提供此方法
// 使用类型构造器F代替Gen、Parser、Option等多种类型,但是这里用到了F[A]的flatMap方法和F[B]的map方法
def map2[A,B,C](fa:F[A], fb:F[B])(f: (A,B) => C): F[C] = fa.flatMap(a => fb.map(b => f(a,b)))
}
1.5 完善Monad接口,使其通过编译,在trait中添加flatMap和map方法
trait Mon[F[_]]{
// 最小的原始操作集合包含flatMap和map
def map[A,B](fa: F[A])(f: A => B): F[B]
def flatMap[A,B](fa: F[A])(f: A => F[B]): F[B]
// 使用原始操作构造组合子map2
def map2[A,B,C](fa: F[A], fb:F[B])(f: (A,B) => C): F[C] = flatMap(fa)(a => map(fb)(b => f(a, b)))
}
1.6 改进Monad接口,避免map的重复定义,我们继承之前带有map方法的Functor
trait Monad[F[_]] extends Functor[F]{
// 引入unit,将unit和flatMap作为最小的原始操作集合,构建map和map2组合子
def unit[A](a: => A): F[A]
def flatMap[A,B](ma: F[A])(f: A => B): F[B]
// 使用原始操作集合实现map和map2
def map[A,B](ma: F[A])(f: A => B): F[B] = flatMap(ma)(a => unit(f(a)))
def map2[A,B,C](ma: F[A], mb: F[B])(f: (A, B) => C): F[C] = flatMap(ma)(a => map(mb)(b => f(a, b)))
}
1.7 Monad接口的使用
只需要实现unit和flatMap就可以获得map和map2,买2送2,岂不美哉
val genMonad = new Monad[Gen]{
def unit[A](a: => A): Gen[A] = Gen.unit(a)
def flatMap[A,B](ma: Gen[A])(f: A => Gen[B]): Gen[B] = ma.flatMap(f)
}
1.8 Monad接口的进一步完善:实现更多组合子
trait Monad[F[_]] extends Functor[F]{nad[]
def unit[A](a: => A): F[A]
def flatMap[A, B](ma: F[A])(f: A => B): F[B]
// 以下为赠送的组合子
def map[A,B](ma: F[A])(f: A=>B): F[B] = flatMap(ma)(a => unit(f(a)))
def map2[A,B,C](ma: F[A], mb: F[B])(f: (A,B) => C): F[C] = flatMap(ma)(a => map(mb)(b => f(a,b)))
def product[A,B](ma: F[A], mb: F[B]): F[(A,B)] = map2(ma,mb)((_,_))
}
2. Monad单子法则
2.1 结合法则
2.1.1 问题的引出:生成商品信息与订单
case class Item(name: String, price: Double)
case class Order(item: Item, quantity: Int)
// 生成订单
val genOrder: Gen[Order] = for{
name <- Gen.stringN(3) // 商品名称为3个字符的随机字符串
price <- Gen.uniform.map(_ * 10) // 0-10之间的随机小叔
quantity <- Gen.choose(1,100) // 0-100之间的随机整数
} yield Order(Item(name, price), quantity)//Item在内部生成
// 使用函数式编程进行改写为x.flatMap(a => f(a).flatMap(g))
Gen.nextString.flatMap(name =>
Gen.nextDouble.flatMap(price =>
Gen.nextInt.map(quantity =>
Order(Item(name, price), quantity) // 依次遍历name、price、quantity生成订单
)
)
)
// 生成商品代码单独抽取出来
val genItem: Gen[Item] = for{
name <- Gen.stringN(3)
price <- Gen.uniform.map(_ * 10)
} yield Item(name, price)
// 生成商品的代码代入生成订单的代码
val genOrder: Gen[Order] = for{
item <- genItem
quantity <- Gen.choose(1, 100)
} yield Order(item, quantity)
// 使用函数式编程进行改写为x.flatMap(f).flatMap(g)
Gen.nextString.flatMap(name =>
Gen.nextInt.map(price =>
Item(name, price) //先遍历name、price生成商品
)
).flatMap(item =>
Gen.nextInt.map(quantity =>
Order(item, quantity) //遍历item、quantity生成订单
)
)
2.1.2 证明法则
x.flatMap(f).flatMap(g) == x.flatMap(a => f(a).flatMap(g))
some(v).flatMap(f).flatMap(g) == som(v).flatMap(a => f(a).flatMap(g))
f(v).flatMap(g) == (a => f(a).flatMap(g))(v)
f(v).flatMap(g) == f(v).flatMap(g)
2.2 Kleisli组合法则
op(op(x,y), z) == op(x, op(y,z))
2.3 恒等法则/单位元法则
monad的unit方法可以看作是monoid的单位元元素
monad的compose方法可以看作是monoid的op方法
compose(f, unit) == f
compose(unit, f) == f
3. Monoad补充知识
3.1 Monad构成
- Monad是一个values的容器,并且这个容器必须有一个flatMap和一个unit(v)操作
- flatMap将Monad中的一个值转换为仍在相同monad类型中的另一个值
- unit(v),用来包装一个values,比如Some(v),Success(v),List(v)
- 所有monad都可以直接实现flatMap,但是每个具体的monad必须自己实现unit(v),在scala里,一般通过构造函数或伴生对象的apply方法来实现
- map是flatMap的一个特殊形式,map方法不是monad方法所必须的:def map[U](f: (T) => U):Monad[U] = flatMap(v => unit(f(v)))
3.2 Monad产生原因
- 解决结果的不确定性问题:对于一连串的操作,任一环节的操作的结果是不确定的,可能得到值或者异常,需要尽可能一切正常的连续调用一系列操作
- 解决副作用的问题:无法用函数式完美解决IO操作,IO无论如何都要伴随副作用
3.3 Monad的本质及其相关概念
3.3.1 Monad本质
Monad本质:一个Monad单子是自函子范畴上的一个幺半群
3.3.2 群的概念
群的定义:G为非空集合,如果在G上定义的二元运算*满足以下性质,则称(G, *)是群,简称G是群
- 封闭性(closure):对于任意a,b属于G,有a*b属于G
- 结合律(associativity):对于任意a,b,c属于G,有(ab)c=a(bc)
- 幺元(identity):存在幺元e,是的对任意a属于G,有ea=ae=a
- 逆元:对于任意a属于G,存在逆元a-1,使得a-1a=aa-1=e
3.3.3 半群的概念
半群:仅满足封闭性、结合律、幺元,则成G为一个含幺半群(Monoid)
3.3.4 代码实现
trait Monad[+T]{
def flatMap[U](f: (T) => Monad[U]): Monad[U] // 定义二元运算,满足封闭性和结合律
def unit(value: B): Monad[B] // 定义幺元e,对于任意a属于G,都有e*a=a*e=a
}
3.3.5 Monad方法特性
- 结合律/封闭性:
monad.flatMap(f).flatMap(g) == monad.flatMap(v => f(v).flatMap(g))
val multiplier : Int => Option[Int] = v => Some(v * v)
val divider : Int => Option[Int] = v => Some(v/2)
val original = Some(10)
original.flatMap(multiplier).flatMap(divider) === original.flatMap(v => multiplier(v).flatMap(divider))
- 左幺元:
unit(x).flatMap(f) == f(x)
val multiplier : Int => Option[Int] = v => Some(v * v)
val item = Some(10).flatMap(multiplier)
item === multiplier(10)
- 右幺元:
monad.flatMap(unit) == monad
val value = Some(50).flatMap(v => Some(v))
value === Some(50)