scala 函子 单子 Functor,Applicative和Monad

函数式编程更偏向于输入和输出

interface A{

piblice boolean apply(T input);

piblice boolean equals(Object other);

}

转化scala

用 T => Boolean 表示(是不是很神奇)

表达式:有返回值

语句:可执行,但无返回值

scala中 if 就是一个表达式

scala 大部分语句是返回最后一个表达式的值作为结果

1、Functor 函子T[_] (态射)

面向函数编程 其实 讲的是一种范畴论,即一种转化,从一种范畴转化到另一种范畴,用态射表示其中的映射关系 例如 flatmap和map。

T[_]:

猫[黄色]    猫[红色]

狗[黄色]    狗[红色]

猫 -》 狗  ==== 函子

黄色 -》 红色 === 态射

函子的一个典型应用就是 map 和 flapmap(拆包和解包)

2、Applicative

我们把类似Option这一类值当作封装过的值,Functor可以把这类封装过的值拆包,并执行函数映射。但是,如果 Function 也是被包装过的,Functor还能发挥作用吗?看一个实验:


我们目的是要遍历 op1,让其内部执行 (x:Int) => x * x,也就是把 op1的 数值1 拿出来执行 1 * 1 = 1

op1.map(fop _) 报错了,通过代码也可以看出这是不可行的。而 fop 是一个Option类型,如果执行内部函数应该使用map方法

scala> op1.map(o=>fop.map(f => o))

res2: Option[Option[Int]] = Some(Some(1))

如上虽然通过map函数可以执行到函数但是返回值却不是我们期望的:Option[Option[Int]]。

应该返回 1 Int类型 。OK,Applicative出场。

Applicative的作用是: 某个值去调用封装函数里的函数,如下:

scala> val f1 = (x: Int) => x + 1

f1: Int => Int =

scala> val f2 = (x: Int) => x + 2

f2: Int => Int =

scala> val fs = List(f1, f2)  // 对 f1 和 f2 进行封装

fs: List [Int => Int] = List(, )

scala> for(f <- fs; x <- List(1, 2, 3)) yield f(x)

res15: List[Int] = List(2, 3, 4, 3, 4, 5)

List(f1, f2) 映射 List(1, 2, 3),映射之后得到了List(2, 3, 4, 3, 4, 5)

即 封装过的函数 List(f1, f2) 返回了 传一个值,返回了 调用 f1函数 和 f2函数过后的值 (当然也可以加case 模式达到只调用一个函数的目的) 。

3、Monad 单子 ,我理解的就是一个封装了某种行为的一个函数,我们可以将它传来传去。

Monad的典型应用就是flatMap,它封装了压缩操作

比如 List( List(1), List(2) ) flat 之后是 List(1, 2)

Some( Some(2) ) flat之后是 Some(2)

=================================================================

下面的例子实现的功能:创建一个工作流,先读入一个文件,做一些计算,然后写出计算结果

展示了,函子、单子、和 for(工作流---即代码从上到下执行)。

// 注:如果一个类是函子或单子,那就可以用for表达式来操作里面的类型

//我们可以封装某代码片段,定义为一类对象,然后传来传去,

// 或者用某种依赖注入方法,注入到框架。

// 例如,当我们需要一个类似 依次执行的管道而不立即执行的的行为时,使用单子是非常有效的。可以用单子来控制和约束该行为

import scalax.functional.{Applicative,Functor,Monad}

trait ManagedResource[T] {

  // 在单子里用到了懒加载,就是说单子只返回了一个含有某种操作的对象,当调用loan函数时才懒加载才触发

  def loan[U](f: T => U): U

}

// 文件读、写操作的封装

object ManagedResource {

  def readFile(file: java.io.File) = new ManagedResource[java.io.InputStream] {

    def loan[U](f: java.io.InputStream => U): U = {

      val stream = new java.io.BufferedInputStream(new java.io.FileInputStream(file))

      try {

        f(stream)// 有括号,函数调用

      } finally {

        stream.close()

      }

    }

  }

  def writeFile(file: java.io.File) = new ManagedResource[java.io.OutputStream] {

    def loan[U](f: java.io.OutputStream => U): U = {

      val stream = new java.io.BufferedOutputStream(new java.io.FileOutputStream(file))

      try {

        f(stream)

      } finally {

        stream.close()

      }

    }

  }

  def make[T <: { def close(): Unit }](t: => T): ManagedResource[T] =

    new ManagedResource[T] {

      def loan[U](f: T => U): U = {

        val resource = t

        try {

          f(resource)

        } finally {

          resource.close()

        }

      }

      override def toString = "ManagedResource(...)"

    }

  // 有了函子和单子的隐式实现,scala就可以在上下文中找到它

  // 函子的apply方法:当调用loan(借给)方法时,把a “ 借用给 f”

  // 函子的map方法:当调用loan(借给)方法时,即调用ma的租借方法,参数数 用mapping包装f后的值

  implicit object MRFunctor extends Functor[ManagedResource] {

    override def apply[A](a: A) = new ManagedResource[A] {

      override def loan[U](f: A => U) = f(a)

      override def toString = "ManagedResource("+a+")"

    }

    override def map[A,B](ma: ManagedResource[A])(mapping: A => B) = new ManagedResource[B] {

      override def loan[U](f: B => U) = ma.loan(mapping andThen f)

      override def toString = "ManagedResource.map("+ma+")("+mapping+")"

    }

  }

  // 单子的 flatten 方法实现 先调用外层资源的 loan 方法,然后再调用外层资源返回的内部资源的 loan 方法

  implicit object MRMonad extends Monad[ManagedResource] {

    override def flatten[A](mma: ManagedResource[ManagedResource[A]]): ManagedResource[A] =

      new ManagedResource[A] {

        override def loan[U](f: A => U): U = mma.loan(ma => ma.loan(f))

        override def toString = "ManagedResource.flatten("+mma+")"

      }

  }

  implicit object MRApplicative extends Applicative[ManagedResource] {

    override def lift2[A,B](func: ManagedResource[A=>B])(ma: ManagedResource[A]): ManagedResource[B] =

      new ManagedResource[B] {

        override def loan[U](f: B => U): U = func.loan(n => ma.loan(n andThen f))

        override def toString = "ManagedResource.lift2("+func+")("+ma+")"

      }

  }

}

=========================================================

import scalax.functional.{Applicative, Functor, Monad, Implicits}

import Implicits._

import java.io._

object Example {

  type LazyTraversable[T] = collection.TraversableView[T, Traversable[T]]

  // 函数是被调用,所以用接受。

  // makeLineTraversable 方法接受一个input参数,返回一个 Traversable[String]对象

  // foreach 方法调用readLine,直到全部读完。每读出一行,把它喂给匿名函数 f

  // 最后调用view方法返回惰性求值的文本行 集合(A[B] 表示 B集合)

  // type LazyTraversable[T] = collection.TraversableView[T,Traversable[T]]

  def makeLineTraversable(input: BufferedReader) = new Traversable[String] {

    def foreach[U](f: String => U): Unit = {

      var line = input.readLine()

      while (line != null) {

        f(line)

        line = input.readLine()

      }

    }

  } view

  // 函数是被调用,所以用接受。

  // getLines方法接受一个 file文件对象,返回一个包含字符串的 ManagedResource 集合

  // 用for表达式,表达一个工作流

  // 调用 readFile 方法,返回 输入流,并包装成buffered

  // 最后把 buffered 传给 makeLineTraversable来构造一个 LazyTraversable[T] 返回。

  // 注:如果一个类是函子或单子,那就可以用for表达式来操作里面的类型

  def getLines(file: File): ManagedResource[LazyTraversable[String]] =

    for {

      input <- ManagedResource.readFile(file)

      val reader = new InputStreamReader(input)

      val buffered = new BufferedReader(reader)

    } yield makeLineTraversable(buffered)  

  // 现在需要逐行读入,并计算每行的长度,计算结果写入一个新文件。

  // 下面定一个新工作流来实现

  // lineLedngthCount方法接受两个参数,最后将结果写入新文件

  // 调用 getLines 返回所有行的 TraversableView

  // 然后对每一行调用 length 计算长度,并将结果与行号组合。然后封装流

  // 最后 BufferedWriter 写入新文件

  //--------------------------------------------------------------------------------------------------

  // 注这个方法并不执行任何计算,它只是返回一个 ManagedResource[Unit]

  // 这个单子的 loan 方法被调用时才读取、计算、并写入结果。这个工作流只是组合了计算行长度的行为

  // 而它并不实际执行。这样就带来了一些灵活性,我们可以将该代码片段,定义为一类对象,然后传来传去,

  // 或者用某种依赖注入方法,注入到框架。

  // 当我们需要一个类似 依次执行的管道而不立即执行的的行为时,使用单子是非常有效的。可以用单子来控制和约束该行为

  def lineLengthCount(inFile: File, outFile: File) =

    for {

      lines <- getLines(inFile)

      val counts = lines.map(_.length).toSeq.zipWithIndex

      output <- ManagedResource.writeFile(outFile)

      val writer = new OutputStreamWriter(output)

      val buffered = new BufferedWriter(writer)

    } yield buffered.write(counts.mkString("\n"))

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

      workflow(new File("test.in"), new File("test.out")).loan(_ => ())

    }

}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,513评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,312评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,124评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,529评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,937评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,913评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,084评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,816评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,593评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,788评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,267评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,601评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,265评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,158评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,953评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,066评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,852评论 2 277

推荐阅读更多精彩内容

  • Scala与Java的关系 Scala与Java的关系是非常紧密的!! 因为Scala是基于Java虚拟机,也就是...
    灯火gg阅读 3,349评论 1 24
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,108评论 18 139
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,620评论 0 10
  • 前几天上映的纪录片《天梯:蔡国强的天梯》,家附近的影院都没排,昨天和朋友在蓝港见面,临时起意跑去最近的一家有排片的...
    腻虫阅读 984评论 1 1
  • 文/小珞 刚下飞机的我听到了意外的噩耗,一个黄先生的朋友,算不上挚交的M走了,原因不详。 因为相处不深,在我印象里...
    小珞的碎碎念阅读 442评论 4 4