Monoid在Scala中的应用笔记

软考 系统架构设计师系列知识点之大数据设计理论与实践(7) 阅读详情

这个例子来源于scala圣经级教程《Functional Programming in Scala》,虽然原书的随书代码可以找到这些类的影子,但却没有关于如何使用这些类的示例代码,本人在阅读此书期间,除了跟着书中的代码敲了一遍之外,还写了一些测试代码进行验证,贴出来作为blog主要是为了方便自己,也为那些同样在阅读此书的人参考。注释不多,本人也不可能写太多,因为这本书不是简单的入门书,而是一本进阶,升华用的内涵书籍,三言两语也解释不清其中的很多细节,如果看不懂,但是又感兴趣的就去看原书吧……


package monoids

import parallelism.Nonblocking.Par.toParOps
import parallelism.Nonblocking._

import scala.language.{higherKinds, implicitConversions}

trait Monoid[A] {
  def op(a1: A, a2: A): A

  def zero: A
}

object Monoid {

  val stringMonoid = new Monoid[String] {
    def zero = ""

    def op(a1: String, a2: String) = a1 + a2
  }


  def listMonoid[A] = new Monoid[List[A]] {
    def op(a1: List[A], a2: List[A]) = a1 ++ a2

    val zero = Nil
  }

  val intAddition: Monoid[Int] = new Monoid[Int] {
    def zero: Int = 0

    def op(x: Int, y: Int): Int = x + y
  }

  val intMultiplication: Monoid[Int] = new Monoid[Int] {
    def op(x: Int, y: Int) = x * y

    val zero = 1
  }

  val booleanOr: Monoid[Boolean] = new Monoid[Boolean] {
    def op(x: Boolean, y: Boolean) = x || y

    val zero = false
  }

  val booleanAnd: Monoid[Boolean] = new Monoid[Boolean] {
    def op(x: Boolean, y: Boolean) = x && y

    val zero = true
  }

  // Notice that we have a choice in how we implement `op`.
  // We can compose the options in either order. Both of those implementations
  // satisfy the monoid laws, but they are not equivalent.
  // This is true in general--that is, every monoid has a _dual_ where the
  // `op` combines things in the opposite order. Monoids like `booleanOr` and
  // `intAddition` are equivalent to their duals because their `op` is commutative
  // as well as associative.
  def optionMonoid[A]: Monoid[Option[A]] = new Monoid[Option[A]] {
    def zero: Option[A] = None

    def op(a1: Option[A], a2: Option[A]): Option[A] = a1 orElse a2
  }

  // We can get the dual of any monoid just by flipping the `op`.
  def dual[A](m: Monoid[A]): Monoid[A] = new Monoid[A] {
    def zero: A = m.zero

    def op(x: A, y: A): A = m.op(y, x)
  }

  // Now we can have both monoids on hand
  def firstOptionMonoid[A]: Monoid[Option[A]] = optionMonoid[A]

  def lastOptionMonoid[A]: Monoid[Option[A]] = dual(firstOptionMonoid)


  // There is a choice of implementation here as well.
  // Do we implement it as `f compose g` or `f andThen g`? We have to pick one.
  def endoMonoid[A]: Monoid[A => A] = new Monoid[A => A] {
    def zero: A => A = (a: A) => a

    def op(f: A => A, g: A => A): A => A = f compose g
  }

  import testing._
  import testing.Prop._


  def monoidLaws[A](m: Monoid[A], gen: Gen[A]): Prop =
  // Associativity
    forAll(for {
      x <- gen
      y <- gen
      z <- gen
    } yield (x, y, z))(p =>
      m.op(p._1, m.op(p._2, p._3)) == m.op(m.op(p._1, p._2), p._3)) &&
      // Identity
      forAll(gen)((a: A) => m.op(a, m.zero) == a && m.op(m.zero, a) == a)


  def concatenate[A](as: List[A], m: Monoid[A]): A = as.foldLeft(m.zero)(m.op)

  // Notice that this function does not require the use of `map` at all.
  // All we need is `foldLeft`.
  def foldMap[A, B](as: List[A], m: Monoid[B])(f: A => B): B = as.foldLeft(m.zero)((b, a) => m.op(b, f(a)))

  // The function type `(A, B) => B`, when curried, is `A => (B => B)`.
  // And of course, `B => B` is a monoid for any `B` (via function composition).
  def foldRight[A, B](as: List[A])(z: B)(f: (A, B) => B): B = foldMap(as, endoMonoid[B])(f.curried)(z)


  // Folding to the left is the same except we flip the arguments to
  // the function `f` to put the `B` on the correct side.
  // Then we have to also "flip" the monoid so that it operates from left to right.
  def foldLeft[A, B](as: List[A])(z: B)(f: (B, A) => B): B = foldMap(as, dual(endoMonoid[B]))(a => b => f(b, a))(z)


  def foldMapV[A, B](as: IndexedSeq[A], m: Monoid[B])(f: A => B): B =
    if (as.length == 0)
      m.zero
    else if (as.length == 1) f(as(0))
    else {
      val (l, r) = as.splitAt(as.length / 2)
      m.op(foldMapV(l, m)(f), foldMapV(r, m)(f))
    }

  // This implementation detects only ascending order,
  // but you can write a monoid that detects both ascending and descending
  // order if you like.
  def ordered(ints: IndexedSeq[Int]): Boolean = {
    // Our monoid tracks the minimum and maximum element seen so far
    // as well as whether the elements are so far ordered.
    val mon = new Monoid[Option[(Int, Int, Boolean)]] {
      def op(o1: Option[(Int, Int, Boolean)], o2: Option[(Int, Int, Boolean)]) =
        (o1, o2) match {
          // The ranges should not overlap if the sequence is ordered.
          case (Some((x1, y1, p)), Some((x2, y2, q))) =>
            Some((x1 min x2, y1 max y2, p && q && y1 <= x2))
          case (x, None) => x
          case (None, x) => x
        }

      val zero = None
    }
    // The empty sequence is ordered, and each element by itself is ordered.
    foldMapV(ints, mon)(i => Some((i, i, true))).map(_._3).getOrElse(true)
  }


  // This ability to 'lift' a monoid any monoid to operate within
  // some context (here `Par`) is something we'll discuss more in
  // chapters 11 & 12
  def par[A](m: Monoid[A]): Monoid[Par[A]] = new Monoid[Par[A]] {
    def zero = Par.unit(m.zero)

    def op(a: Par[A], b: Par[A]) = a.map2(b)(m.op)
  }

  // we perform the mapping and the reducing both in parallel
  def parFoldMap[A, B](v: IndexedSeq[A], m: Monoid[B])(f: A => B): Par[B] =
    Par.parMap(v)(f).flatMap { bs =>
      foldMapV(bs, par(m))(b => Par.lazyUnit(b))
    }


  def productMonoid[A, B](A: Monoid[A], B: Monoid[B]): Monoid[(A, B)] =
    new Monoid[(A, B)] {
      def op(x: (A, B), y: (A, B)) = (A.op(x._1, y._1), B.op(x._2, y._2))

      val zero = (A.zero, B.zero)
    }

  def functionMonoid[A, B](B: Monoid[B]): Monoid[A => B] =
    new Monoid[A => B] {
      def op(f: A => B, g: A => B) = a => B.op(f(a), g(a))

      val zero: A => B = a => B.zero
    }

  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]) = (a.keySet ++ b.keySet).foldLeft(zero) { (acc, k) =>
        acc.updated(k, V.op(a.getOrElse(k, V.zero), b.getOrElse(k, V.zero)))
      }
    }


  def bag[A](as: IndexedSeq[A]): Map[A, Int] = foldMapV(as, mapMergeMonoid[A, Int](intAddition))((a: A) => Map(a -> 1))


  sealed trait WC

  case class Stub(chars: String) extends WC

  case class Part(lStub: String, words: Int, rStub: String) extends WC

  val wcMonoid: Monoid[WC] = new Monoid[WC] {
    val zero = Stub("")

    def op(a: WC, b: WC) = (a, b) match {
      case (Stub(c), Stub(d)) => Stub(c + d)
      case (Stub(c), Part(l, w, r)) => Part(c + l, w, r)
      case (Part(l, w, r), Stub(c)) => Part(l, w, r + c)
      case (Part(l1, w1, r1), Part(l2, w2, r2)) => Part(l1, w1 + (if ((r1 + l2).isEmpty) 0 else 1) + w2, r2)
    }
  }

  def count(s: String): Int = {
    // A single character's count. Whitespace does not count,
    // and non-whitespace starts a new Stub.
    def wc(c: Char): WC = if (c.isWhitespace)
      Part("", 0, "")
    else Stub(c.toString)


    // `unstub(s)` is 0 if `s` is empty, otherwise 1.
    def unstub(s: String) = s.length min 1


    foldMapV(s.toIndexedSeq, wcMonoid)(wc) match {
      case Stub(s) => unstub(s)
      case Part(l, w, r) => unstub(l) + w + unstub(r)
    }
  }

}


object MonoidTest {

  import testing._

  def main(args: Array[String]): Unit = {

    //验证Monoid.intAddition的正确性
    Prop.run(Monoid.monoidLaws(Monoid.intAddition, Gen.choose(Int.MinValue, Int.MaxValue)) tag " monoid laws error ")


    val stringLi = List("I ", "am ", "a ", "monster ")

    val stringLiRes = Monoid.concatenate(stringLi, Monoid.stringMonoid)
    println(stringLiRes)


    val intLi = List(1, 3, 5, 7, 9)
    val f = (a: Int) => a.toString + " "
    val intLiFoldMapRes = Monoid.foldMap(intLi, Monoid.stringMonoid)(f)
    println(intLiFoldMapRes)


    val intLi1 = List(1, 2, 3, 4, 5, 6, 7, 8, 9)
    val max = Monoid.foldLeft[Int, Int](intLi1)(0)((b, a) => if (b > a) b else a)
    println(max)


    println("ordered:  " + Monoid.ordered(intLi1.toIndexedSeq))


    println(Monoid.bag[Int](List(1, 2, 3).toIndexedSeq))


    println(Monoid.bag[Int](List(1, 1, 2).toIndexedSeq))

    println("word count: " + Monoid.count("one"))

    println("word count: " + Monoid.count("two words"))

  }

}

上述代码运行结果如下:

+ OK, passed 100 tests.
I am a monster 
1 3 5 7 9 
9
ordered:  true
Map(1 -> 1, 2 -> 1, 3 -> 1)
Map(1 -> 2, 2 -> 1)
word count: 1
word count: 2

879675643@qq.com  lhever

.---.                                                                         
|   |   .              __.....__   .----.     .----.   __.....__              
|   | .'|          .-''         '.  \    \   /    /.-''         '.            
|   |<  |         /     .-''"'-.  `. '   '. /'   //     .-''"'-.  `. .-,.--.  
|   | | |        /     /________\   \|    |'    //     /________\   \|  .-. | 
|   | | | .'''-. |                  ||    ||    ||                  || |  | | 
|   | | |/.'''. \\    .-------------''.   `'   .'\    .-------------'| |  | | 
|   | |  /    | | \    '-.____...---. \        /  \    '-.____...---.| |  '-  
|   | | |     | |  `.             .'   \      /    `.             .' | |      
'---' | |     | |    `''-...... -'      '----'       `''-...... -'   | |      
      | '.    | '.                                                   |_|      
      '---'   '---'  
抽象代数基础 丘维声 北大丘老师的经典书籍,写得很不错,课也上得好,推荐下载 立即下载

相关推荐

monoid-subclasses:Monoid的子类具有扎实的理论基础和实际用途

id半群子类 具有可靠的理论基础和实际用途的Semigroup和Monoid的子类 monoid-subclasses软件包已。 该软件包定义了几个类,这些类比和丰富,但比要求不高: 提供了运算符</> ,它作为半群运算符<>的部分逆。 是Reductive的子类,它提供有关</>操作结果的其他保证: (a <> b) </> a == Just b (a <> b) </> b == Just a 每个组(即,每个带有操作inverse :: a -> a Monoid a inverse :: a -> a Monoid a )都是可取消的Monoid a ,其中a </> b = Just (a <> inverse b)但并非每个Cancellative monoid都是一个组。 是的子类, Reductive和Monoid ,提供了gcd用于获取最大公约数的两个给定

函数式编程 - 有趣的Monoid(单位半群)

前言 Monoid(中文:单位半群,又名:幺半群),一个来源于数学的概念;得益于它的抽象特性,Monoid在函数式编程中起着较为重大的作用。 本篇文章将会以工程的角度去介绍Monoid的相关概念,并结合几个有趣的数据结构(如Middleware、Writer)来展现Monoid自身强大的能力及其实用性。 Semigroup(半群) 在开始Monoid的表演之前,我们首先来感受一下Semigroup...

weixin_34166472的博客 676

WGCNA分析中那些没人告诉你的坑:以肺结核差异基因模块筛选为例

本文深入探讨WGCNA分析在肺结核差异基因模块筛选中的七个关键陷阱,包括soft-thresholding选取、样本离群值检测、模块合并阈值调整等。通过实际案例和R代码示例,揭示生信数据挖掘中常被忽视的技术细节,帮助研究者避免常见错误,提升分析结果的可靠性和生物学意义。

weixin_29218509的博客 262

monoid_什么是Monoid?

monoidAll the good JS folks are into Functional Programming these days. Many of my readers already know about the Monad, and its other Functor friends. But what about that other similar word that you’...

weixin_26750481的博客 427

海康工业相机排雷笔记02-像素格式解析

工业相机使用过程记录 02-像素格式解析 通过将相机输出图像裸数据raw格式直接存图查看 一、黑白/Mono 直接使用相机测试图像存图: 图像中各部分灰度分别为:0、36、73、109、146、182、219、255,以灰度73列为例 (1)Mono 8 该列图像数据为十六进制49,十进制为73,那么Mono 8就是每个像素点占8bit,依次排列。 (2)Mono 10 该列图像数据为十六进制2401,由于相机均为小端数据,实际数据应解析为0124,十进制为292=73*4,那么Mono 10格式为每

yonghuming_66的博客 8361

基于Sentinel-2(哨兵2)遥感卫星的植被覆盖度提取

基于Sentinel-2(哨兵2)遥感卫星的植被覆盖度提取 一、Sentinel-2简单介绍 哨兵-2系列卫星的主要载荷多光谱成像仪拥有13个谱段,从可见光到近红外再到短波红外,空间分辨率从10m到60m,能实现全所未有的陆地海洋监测水平。其幅宽高达290KM,且A、B双星组合,覆盖能力比美国Landsat8卫星强很多。目前哨兵-2A和哨兵-2B已经实现了5天的重访时间,即使在之前仅有单颗卫星时,...

weixin_44143671的博客 2万+

monoid之含义

monoid在代数里面 The prefix "mon-" means "one" 主要三个特性 1.包含一种类型A 2.接受两个参数A的F,进行操作后并返回类型A 3.标识该类型的身份值(我把它理解为进行OP操作类型的底) 所以它的trait trait Monoid[A] { def op(a1: A, a2: A): A def zero: A } 例如 val string

u013571243的专栏 863

Docker部署OnlyOffice踩坑实录:解决editor.bin下载失败的3种姿势

本文详细解析了Docker部署OnlyOffice时editor.bin下载失败的常见原因,并提供了三种核心解决方案。重点剖析了容器网络配置、Nginx反向代理(特别是proxy_pass设置)以及安全策略调整,帮助开发者彻底解决静态资源加载问题,确保文档协作平台稳定运行。

weixin_29281941的博客 873

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

一文带你了解幺半群Monoid

檐前潜心学种瓜 563

从TCGA官网获取新版count数据并精准转换为TPM的实战指南

本文详细介绍了从TCGA官网获取新版count数据并精准转换为TPM的实战指南。通过TCGAbiolinks工具下载原始count数据,结合GENCODE v36注释文件计算基因有效长度,最终实现count到TPM的标准化转换,确保数据完整性和分析可靠性。文章包含完整代码示例和常见问题解决方案,特别适合生物信息学研究人员参考。

weixin_27310417的博客 285

基于carsim与Simulink联合仿真的车辆换道轨迹规划与轨迹跟踪模型

PID_反步控制_滑膜控制三种四旋翼无人机轨迹跟踪控制算法仿真 附带对应的说明文档,提供建模过程与仿真结果文献(英文)➕PPT 描述:采用的同一种参考轨迹,都是三维仿真 三种控制方法: [1]PID控制、[2]反步控制、[3]滑膜控制(三个仿真模型) 每个仿真均应用了同一种轨迹路径,直接运行即可出三维图,可以实现四旋翼无人机轨迹的跟踪

2501_90804007的博客 1085

Scala函数式编程中的Monoid

在本文中,我们将会从一个简单的需求出发,尝试通过对代码的多次重构,逐步帮你理解什么是Monoid。 基本概念 群(category)的基本概念 群(category)有两个组成要素: 由一些同一个类型的对象组成 一种态设关系(map),可以将范畴中的任何一个对象转换成另外一个对象,转换之后的类型还是class的。 其示意图如下所示: 另外,一个群还需要满足两个公理: 结合律,以加法为例,(a+b)+c = a+(b+c) 同一律,群中存在一个特殊的对象e,使得 e * y = y = y

我是兴锅的博客 603

RH850从0搭建Autosar开发环境系列讲解 - 总目录

Davinci Autosar实战开发从0构建开发能力。OS,COM,DCM,BSWMM,ECUM,VLINK,XCP,MEMMAP等模块配置,带你熟悉运行的autosar代码,包括MICROSAR的启动运行,使用GreenHills生成运行的hex。

weixin_42412049的博客 1万+

Scala 3 类型类实战指南:从 given/using 到 Monoid,一次看懂最核心的抽象编程技巧

类型类(Type Class)是 Scala 3 中最具代表性的抽象编程思想,而 `given`/`using` 语法则是它的“翻译官”。本文以 O'Reilly 经典图书《Programming Scala(第3版)》官方代码仓库 programming-scala-book-code-examples 为素材,用新手也能看懂的方式,精讲 Scala 3 类型类实战:如何用 `given`/`u

gitblog_01007的博客 225

Apache Kylin 命令注入漏洞复现(CVE-2020-1956)

Apache Kylin 中的静态 API 存在安全漏洞,可以将os命令与用户输入字符串连接起来,攻击者可以在Kylin没有任何保护或验证的情况下执行任何os命令。

0xSec 1558

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Cats类型类深度解析:Monoid、Functor、Applicative核心概念指南

Cats是Scala语言中一个轻量级、模块化且可扩展的函数式编程库,提供了丰富的类型类抽象来简化复杂的数据处理。在函数式编程中,类型类是构建可组合、可重用代码的核心工具。本文将深入解析Cats中三个最重要的类型类:Monoid、Functor和Applicative,帮助你掌握这些强大的编程概念。✨ ## 什么是类型类?🤔 在开始之前,让我们先理解类型类的概念。类型类是一种抽象机制,允许我们

gitblog_00111的博客 1136

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

monoid之组合

 monoid是允许组合的,而且也是他正真强大的地方(FP IN SCALA 提到),例如 A: Monoid[A], B: Monoid[B] 可以得出 Monoid[B]): Monoid[(A,B)] 也是成立的 我们使用Monoid的组合组合性来装配一个更加复杂的Monoid----合并键值 源码来之FP IN SCALA def mapMergeMonoid[K,

u013571243的专栏 563

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Algebird核心概念解析:从Semigroup到Monoid的完整教程

Algebird是一个基于Scala的抽象代数库,提供了丰富的代数结构实现,帮助开发者在分布式系统中高效处理聚合操作。本文将深入解析Algebird中的核心概念,从Semigroup到Monoid,带你快速掌握这些强大工具的使用方法。 ## 什么是Semigroup? 在抽象代数中,Semigroup(半群)是一个具有单一二元运算的代数结构。在Algebird中,Semigroup被定义为一个

gitblog_00204的博客 642

Scala 入门-隐式参数(implicit)

专栏原创出处:github-源笔记文件 ,github-源码 ,欢迎 Star,转载请附上原文出处链接和本声明。 Scala 编程语言专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Scala 编程语言 在参数列表的开头添加了 implicit 关键字,该参数列表里的参数即为隐式参数。 一个方法只能有一个隐式参数列表,隐式参数列表中可以定义多个隐式参数。 在调用方法时,如果没...

2.wa 1805

scala中的implicit

前言 scala在2.10版本中引入了implicit关键字,包括两个方面的内容:隐式参数和隐式转换。 隐式参数:把一些变量声明为隐式参数,把一些方法的参数声明为可以支持隐式参数的,当调用方法时,如果没有传递该参数,那么可以去使用隐式参数。 隐式转换:首先提供一些类型转换的能力(通过隐式类和隐式方法),然后在调用某些方法时,当某些对象的类型不匹配,可以使用对应的隐式转换去转换对象的类型。 impl...

civil的专栏 1万+

Scalaz(8)- typeclass:Monoid and Foldable

Monoid是种最简单的typeclass类型。我们先看看scalaz的typeclass定义:scalaz/Monoid.scala trait Monoid[F] extends Semigroup[F] { self => //// /** The identity element for `append`. */ def zero: F ... Monoid trait

TIGER_XC的专栏 647

泛函编程(21)-泛函数据类型-Monoid

Monoid是数学范畴理论(category theory)中的一个特殊范畴(category)。不过我并没有打算花时间从范畴理论的角度去介绍Monoid,而是希望从一个程序员的角度去分析Monoid以及它在泛函编程里的作用。从这个思路出发我们很自然得出Monoid就是一种数据类型,或者是一种在泛函编程过程中经常会遇到的数据类型:当我们针对Lis...

weixin_34301307的博客 122

Scala(Scala)的Monad进一步消灭:一种范畴论方法

Some of you may have read my article on monads. This time I want to talk about monads from a different, more theoretical point of view. It is not mandatory to read the previous article first (in case ...

cumian8165的博客 618

Scala Cats函数式编程进阶:掌握类型类与高级抽象构建健壮应用

/ 1. 定义ADT描述操作// 2. 将ADT提升到Free场景推荐工具原因聚合数据SemigroupMonoid结合律保证,可并行简单上下文转换Functor最轻量,关注点单一独立上下文组合可并行执行,错误累积顺序依赖计算Monadfor推导式后续计算依赖前值错误处理MonadErrorEitherT统一错误处理接口累积验证Validated收集所有错误而非中断依赖注入Kleisli函数式Reader状态管理State纯函数状态变更DSL构建Free关注点分离。

✨ 欢迎来到【Seal ^_^ 的优快云博客】!✨ 4064
上一篇: 使用Scala从头实现一个简单的语法分析器组合字库
下一篇: Monad在Scala中的应用笔记
lhever_
博客等级 码龄14年 213粉丝 130原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值