美文网首页
scala 函子 单子 Functor,Applicative和

scala 函子 单子 Functor,Applicative和

作者: scandly | 来源:发表于2018-08-22 13:07 被阅读0次

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

    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(_ => ())

        }

    }

    相关文章

      网友评论

          本文标题:scala 函子 单子 Functor,Applicative和

          本文链接:https://www.haomeiwen.com/subject/axoeiftx.html