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

1. monoid(幺半群)

定义:一个类型,具有二元操作(满足结合律),具有一个单位元元素

  1. 单位元元素和任意元素的结合满足交换律:单位元元素"“和元素"s”,“”+“s"和"s”+""等价(简言之:单位元元素满足交换律)
  2. 任意三个元素满足结合律:接受两个参数然后返回相同类型的值,对于任何x:A,y:A,z:A来说,op(op(x,y),z)和op(x,op(y,z))等价(简言之,任意三个元素满足结合律)
  3. monoid同态:M.op(f(x), f(y)) == f(N.op(x, y)),例如:”字符串x长度和字符串y长度相加的值“等于”字符串x字符串y合并后的新字符串长度“(简言之,任意函数与任意两个元素满足结合律与分配律)
  4. monoid同质:,双向同态M和N之间存在两个同态函数f和g,f andThen g等同于g andThen f,即对于M和N,f(g(x)) == g(f(x))(简言之,复合函数满足交换律,此时为交换幺半群,此时Monoid双向同态)
  5. 实现举例:scala不同于java的一点——trait比java的interface更加灵活,trait可以直接使用new进行实例化,紧跟一对花括号new Monoid{},而interface则必须被继承
// 原型的代数结构,一个monoid类型包含一个单位元元素和一个op操作,单位元元素是A类型,操作的返回类型也是A类型,如果A类型是嵌套类型,那么为了满足monoid同态这一性质,A类型内部的元素类型也必需是A或A的子类型
trait Monoid[A]{
    def zero: A // 单位元元素,也叫幺元,作为初始值,满足同一律,和任意元素的操作都等于自身
    def op(a1: A, a2: A): A // 结合律,op(x, zero)和op(zero, x)等价
}

// trait Monoid[A]的实例化:泛型A如果用到了方法,需要指定具体类型,否则类型推导错误,否则只需维持现状即可;def需要实现过程
// String monoid:字符串类型的Monoid
val stringMonoid = new Monoid[String]{
    def zero = ""
    def op(a1: String, a2: String) = a1 + a2
}

// List monoid:列表类型的Monoid
def listMonoid[A] = new Monoid[List[A]]{
    def zero = Nil
    def op(a1: List[A], a2: List[A]) = a1 ++ a2
}

// Int monoid:整数类型的Monoid
def intAddition = new Monoid[Int]{
    def zero:Int = 0
    def op(a1: Int, a2: Int):Int = a1 + a2
}

2. Monoid应用场景

结合可折叠数据类型使用,可折叠数据结构:List、Tree(所有包含foldRight方法的Foldable数据类型)都是可折叠数据结构,支持foldRight、foldLeft、foldMap操作,不需要关心具体的结构类型

val words = List("one", "two", "three")
// 向右折叠foldRight,从右向左依次进行计算,右侧第一个元素为单位元元素""
// def foldRight[B](z: B)(f: (A, B) => B): B
// 特例,当A和B恰好为同一类型,def foldRight[A](z: A)(f: (A, A) => A): A
// words.foldRight("")(_ + _) == "one" + ("two" + ("three" + ""))
words.foldRight(stringMonoid.zero)(stringMonoid.op)
// 向左折叠foldLeft,从左向右依次进行计算,左侧第一个元素为单位元元素""
// def foldLeft[B](z: B)(f: (B, A) => B): B
// 特例,当A和B恰好为同一类型,def foldLeft[A](z: A)(f: (A, A) => A): A
// words.foldLeft("")(_ + _) == (("" + "one") + "two") + "three"
words.foldLeft(stringMonoid.zero)(stringMonoid.op)


// 通用描述
trait Foldable[F[_]]{ // 类型构造器F[_],F不是一个类型而是一个类型构造器,接受一个类型参数,Foldable是高阶类型构造函数或者高阶类型
    def foldRight[A, B](as: F[A])(z: B)(f: (A, B) => B): B
    def foldLeft[A, B](as: F[A])(z: B)(f: (B, A) => B): B
    def foldMap[A, B](as: F[A])(f: A => B)(mb: Monoid[B]): B // 下文会提及
    def concatenate[A](as: F[A])(m: Monoid[A]): A = foldLeft(as)(m.zero)(m.op) // 通用折叠方式,替代foldLeft和foldRight
}

3. 组合monoid

多个具体类型A、B,都是monoid,那么tuple类型(A,B)同样是monoid

组合的前提:只要包含的元素是monoid,数据结构就能构建成monoid,以下是《scala函数式编程》里的例子

  1. 定义组合monoid用于合并Map(对相同键对应的值进行加法操作)
// 定义一个monoid组合子,用于合并key-value Map,前提:所有需要用到op的值类型都是Monoid,这里的v需要用到op,因此v是Monoid[V]类型
// 使用monoid可以构建通用的处理函数,通过抽象出统一的单位元元素作为迭代的初始状态、统一的op操作作为迭代的具体操作,可以通过组合子的方式对嵌套类型进行处理
def mapMergeMonoid[K, V](v: Monoid[V]): Monoid[Map[K, V]] = 
    new Monoid[Map[K, V]]{
    def zero = Map[K, V]()
    def op(a: Map[K, V], b: Map[K, V]) = 
        // 花括号内为匿名函数,foldLeft方法,acc初始值为zero,acc.updated其实是Map.updated
        (a.keySet ++ b.keySet).foldLeft(zero) {(acc, k) => acc.updated(k, v.op(a.getOrElse(k, v.zero), b.getOrElse(k, v.zero)))}
}
// 清晰简洁的组合子处理嵌套类型,不需要额外构建嵌套表达式
val M: Monoid[Map[String, Map[String, Int]]] = mapMergeMonoid(mapMergeMonoid(intAddition))
val m1 = Map("o1" -> Map("i1" -> 1, "i2" -> 2))
val m2 = Map("o1" -> Map("i2" -> 3))
val m3 = M.op(m1, m2)
  1. 定义组合monoid用于实现多个List[Int]的累加

方案一:不使用monoid,完成多个List之间的累加、累乘

// 方案一:不使用monoid,完成多个List之间的累加、累乘
def sum(list: List[Int]): Int = {
    list.foldLeft(0)((res, a) => res + a)
}

sum(List(1,2,3,4,5))

def multi(list: List[Int]):Int = {
    list.foldLeft(1)((res,a) => res * a)
}

multi(List(1,2,3,4,5))

方案二:定义一个fold函数,对sum和multi进行抽象

// 方案二:定义一个fold函数,对sum和multi进行抽象
// 先定义一个幺半群
trait Monoid[A]{
    def zero:A
    def combine(a1:A, a2:A):A
}

def fold(list:List[Int], m:Monoid[Int]): Int = {
    list.foldLeft(m.zero)(m.combine)
}

val sumMonoid:Monoid[Int] = new Monoid[Int]{
    def zero: Int = 0
    def combine(a1: Int, a2:Int):Int = a1 + a2
}

val mulMonoid:Monoid[Int] = new Monoid[Int]{
    def zero:Int = 0
    def combine(a1:Int, a2:Int):Int = a1 * a2
}

fold(List(1,2,3,4,5), sumMonoid)
fold(List(1,2,3,4,5), mulMonoid)

方案三:fold函数只能处理Int类型,不能处理Double或String类型对象,引入泛型参数

// 方案三:fold函数只能处理Int类型,不能处理Double或String类型对象,引入泛型参数
trait Monoid[A]{
    def zero:A
    def combine(a1: A, a2: A): A
}

def fold[A](list:List[A], m:Monoid[A]):A = {
    list.foldLeft(m.zero)(m.combine)
}

val stringMonoid:Monoid[String] = new Monoid[String]{
    def zero:String = ""
    def combine(a1:String, a2:String):String = x + y
}

fold(List("one", "two", "three"), stringMonoid)

方案四:fold函数只能处理List[A]类型,支持foldLeft的还有Set、Map,也希望能使用fold函数,引入泛型构造器

// 方案四:fold函数只能处理List[A]类型,支持foldLeft的还有Set、Map,也希望能使用fold函数,引入泛型构造器
trait Monoid[A]{
    def zero:A
    def combine(a1:A, a2:A):A
}
// 这样写会报错,因为并不是所有类型都有foldLeft方法
def fold[F[_], A](list: F[A])(m:Monoid[A]):A = {
    list.foldLeft(m.zero)(m.combine)
}

// 写一个特质,为类型构造器添加一个foldLeft方法
trait Foldable[F[_]]{ // F[_]表示F是一个容器,否则在下文中使用F[A]会报错
    def foldLeft[A](fa: F[A])(zero:A)(f: (A, A) => A): A
}
// 使用隐式参数,为传入的f添加foldLeft方法
def fold[F[_], A](list: F[A])(m: Monoid[A])(implicit f: Foldable[F]): A = {
    f.foldLeft(list)(m.zero)(m.combine)
}
// 使用Context Bound,约束泛型构造器的类型为Foldable
def fold[F[_]:Foldable, A](list: F[A])(m:Monoid[A]):A = {
    implicitly[Foldable[F]].foldLeft(list)(m.zero)(m.combine)
}
// 为了能使用隐式参数或隐式视图,需要定义隐式变量。。。
implicit val listFoldable:Foldable[List] = new Foldable[List]{
    def foldLeft[A](fa:List[A])(zero:A)(f: (A, A) => A): A = fa.foldLeft(zero)(f)
}
// F部分的补丁打完了,我们需要完成剩下的Monoid
implicit val sumMonoid: Monoid[Int] = new Monoid[Int]{
    def zero:Int = 0
    def combine(x:Int, y:Int):Int = x + y
}

方案五:把方案四中的隐式值通过上下文约束context bound,自动引入

// 方案五:把方案四中的隐式值通过上下文约束context bound,自动引入
def fold[F[_]:Foldable, A:Monoid](list:F[A]):A = {
    val m = implicitly[Monoid[A]]
    val f = implicitly[Foldable[F]]
    f.foldLeft(list)(m.zero)(m.combine)
}

// 方案六:对方案五中的context bound进行优化,使用隐式转换参数,自动引入
def fold[F[_], A](as: F[A])(implicit evf: Foldable[F], eva:Monoid[A]):A = {
    evf.foldLeft(as)(eva.zero)(eva.combine)
}

implicit val sumMonoid: Monoid[Int] = new Monoid[Int]{
    def zero: Int = 0
    def combine(x: Int, y:Int):Int = x + y
}

implicit val listFoldable:Foldable[List] = new Foldable[List]{
    def foldLeft[A](fa:List[A])(zero: A)(f: (A, A) => A): A = fa.foldLeft(zero)(f)
}

fold(List(1,2,3,4,5))
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

鱼摆摆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值