【scala函数式编程】函数设计的通用结构——Monad

1. monad单子(monads)

定义:monad是一个满足associativity和identity法则的monadic组合的最小集合的实现,是对象类型的包装,用一个对象包装另一个对象,monad单子既不是trait特质也不是class类,大多数集合类型是单子,但反之不成立

补充:

  1. Functor函子——类别之间的映射,换言之,是高阶类型之间的映射,高阶类型是以类型构造器为参数的类型,举个例子,Int、List[Int]、List[T]是一阶类型,List[C[Int]]、List[C[T]]是高阶类型
  2. 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不会强加任何法则

  1. 可能存在翻译问题,函子法则其实是指抽象过程中应当注意到的类型约束,并学会用==去时刻注意是否满足这些类型约束条件,防止出现编译正确但计算错误的情形
  2. 法则帮助接口定义了语法,方便代数定义在实例间进行独立地推演,例如,Monoid[A]和Monoid[B]构建Monoid[(A,B)]也是满足结合律的
  3. 法则帮助抽象接口中的函数编写各种组合子,例如,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构成

  1. Monad是一个values的容器,并且这个容器必须有一个flatMap和一个unit(v)操作
  2. flatMap将Monad中的一个值转换为仍在相同monad类型中的另一个值
  3. unit(v),用来包装一个values,比如Some(v),Success(v),List(v)
  4. 所有monad都可以直接实现flatMap,但是每个具体的monad必须自己实现unit(v),在scala里,一般通过构造函数或伴生对象的apply方法来实现
  5. map是flatMap的一个特殊形式,map方法不是monad方法所必须的:def map[U](f: (T) => U):Monad[U] = flatMap(v => unit(f(v)))

3.2 Monad产生原因

  1. 解决结果的不确定性问题:对于一连串的操作,任一环节的操作的结果是不确定的,可能得到值或者异常,需要尽可能一切正常的连续调用一系列操作
  2. 解决副作用的问题:无法用函数式完美解决IO操作,IO无论如何都要伴随副作用

3.3 Monad的本质及其相关概念

3.3.1 Monad本质

Monad本质:一个Monad单子是自函子范畴上的一个幺半群

3.3.2 群的概念

群的定义:G为非空集合,如果在G上定义的二元运算*满足以下性质,则称(G, *)是群,简称G是群

  1. 封闭性(closure):对于任意a,b属于G,有a*b属于G
  2. 结合律(associativity):对于任意a,b,c属于G,有(ab)c=a(bc)
  3. 幺元(identity):存在幺元e,是的对任意a属于G,有ea=ae=a
  4. 逆元:对于任意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方法特性

  1. 结合律/封闭性:
    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))
  1. 左幺元:
    unit(x).flatMap(f) == f(x)
val multiplier : Int => Option[Int] = v => Some(v * v)
val item = Some(10).flatMap(multiplier)
item === multiplier(10)
  1. 右幺元:
    monad.flatMap(unit) == monad
val value = Some(50).flatMap(v => Some(v))  
value === Some(50)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鱼摆摆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值