Scalaz(34)- Free :算法-Interpretation

  我们说过自由数据结构(free structures)是表达数据类型的最简单结构。List[A]是个数据结构,它是生成A类型Monoid的最简单结构,因为我们可以用List的状态cons和Nil来分别代表Monoid的append和zero。Free[S,A]是个代表Monad的最简单数据结构,它可以把任何Functor S升格成Monad。Free的两个结构Suspend,Return分别代表了Monad的基本操作函数flatMap,point,我特别强调结构的意思是希望大家能意识到那就是内存heap上的一块空间。我们同样可以简单的把Functor视为一种算法,通过它的map函数实现运算。我们现在可以把Monad的算法flatMap用Suspend[S[Free[S,A]]来表示,那么一段由Functor S(ADT)形成的程序(AST)可以用一串递归结构表达:Suspend(S(Suspend(S(Suspend(S(....(Return)))))))。我们可以把这样的AST看成是一串链接的内存格,每个格内存放着一个算法ADT,代表下一个运算步骤,每个格子指向下一个形成一串连续的算法,组成了一个完整的程序(AST)。最明显的分别是Free把Monad flatMap这种递归算法化解成内存数据结构,用内存地址指向代替了递归算法必须的内存堆栈(stack)。Free的Interpretation就是对存放在数据结构Suspend内的算法(ADT)进行实际运算。不同方式的Interpreter决定了这段由一连串ADT形成的AST的具体效果。

Free Interpreter的具体功能就是按存放在数据结构Suspend内的算法(ADT)进行运算后最终获取A值。这些算法的实际运算可能会产生副作用,比如IO算法的具体操作。scalaz是通过几个运算函数来提供Free Interpreter,包括:fold,foldMap,foldRun,runFC,runM。我们先看看这几个函数的源代码:

 /** Catamorphism. Run the first given function if Return, otherwise, the second given function. */
  final def fold[B](r: A => B, s: S[Free[S, A]] => B)(implicit S: Functor[S]): B =
    resume.fold(s, r)

  /**
   * Catamorphism for `Free`.
   * Runs to completion, mapping the suspension with the given transformation at each step and
   * accumulating into the monad `M`.
   */
  final def foldMap[M[_]](f: S ~> M)(implicit S: Functor[S], M: Monad[M]): M[A] =
    this.resume match {
      case -\/(s) => Monad[M].bind(f(s))(_.foldMap(f))
      case \/-(r) => Monad[M].pure(r)
    }

  /** Runs to completion, allowing the resumption function to thread an arbitrary state of type `B`. */
  final def foldRun[B](b: B)(f: (B, S[Free[S, A]]) => (B, Free[S, A]))(implicit S: Functor[S]): (B, A) = {
    @tailrec def foldRun2(t: Free[S, A], z: B): (B, A) = t.resume match {
      case -\/(s) =>
        val (b1, s1) = f(z, s)
        foldRun2(s1, b1)
      case \/-(r) => (z, r)
    }
    foldRun2(this, b)
  }

  /**
   * Runs to completion, using a function that maps the resumption from `S` to a monad `M`.
   * @since 7.0.1
   */
  final def runM[M[_]](f: S[Free[S, A]] => M[Free[S, A]])(implicit S: Functor[S], M: Monad[M]): M[A] = {
    def runM2(t: Free[S, A]): M[A] = t.resume match {
      case -\/(s) => Monad[M].bind(f(s))(runM2)
      case \/-(r) => Monad[M].pure(r)
    }
    runM2(this)
  }

  /** Interpret a free monad over a free functor of `S` via natural transformation to monad `M`. */
  def runFC[S[_], M[_], A](sa: FreeC[S, A])(interp: S ~> M)(implicit M: Monad[M]): M[A] =
    sa.foldMap[M](new (({type λ[α] = Coyoneda[S, α]})#λ ~> M) {
      def apply[A](cy: Coyoneda[S, A]): M[A] =
        M.map(interp(cy.fi))(cy.k)
      })

我们应该可以看出Interpreter的基本原理就是把不可运算的抽象指令ADT转换成可运算的表达式。在这个转换过程中产生运算结果。我们下面用具体例子一个一个介绍这几个函数的用法。还是用上期的例子:

1 object qz {
 2 sealed trait Quiz[+Next]
 3 object Quiz {
 4 //问题que:String, 等待String 然后转成数字或操作符号
 5   case class Question[Next](que: String, n: String => Next) extends Quiz[Next]
 6   case class Answer[Next](ans: String, n: Next) extends Quiz[Next]
 7   implicit object QFunctor extends Functor[Quiz] {
 8     def map[A,B](qa: Quiz[A])(f: A => B): Quiz[B] =
 9       qa match {
10          case q: Question[A] => Question(q.que, q.n andThen f)
11          case Answer(a,n) => Answer(a,f(n))
12       }
13   }
14 //操作帮助方法helper methods
15   def askNumber(q: String) = Question(q, (inputString => inputString.toInt))  //_.toInt
16   def askOperator(q: String) = Question(q, (inputString => inputString.head.toUpper.toChar)) //_.head.toUpper.toChar
17   def answer(fnum: Int, snum: Int, opr: Char) = {
18     def result =
19       opr match {
20         case 'A' => fnum + snum
21         case 'M' => fnum * snum
22         case 'D' => fnum / snum
23         case 'S' => fnum - snum
24       }
25     Answer("my answer is: " + result.toString,())
26   }
27   implicit def quizToFree[A](qz: Quiz[A]): Free[Quiz,A] = Free.liftF(qz)
28  }
29 import Quiz._
30 val prg = for {
31  fn <- askNumber("The first number is:")
32  sn <- askNumber("The second number is:")
33  op <- askOperator("The operation is:")
34  _ <- answer(fn,sn,op)
35 } yield()       

prg是一段功能描述:在提示后读取一个数字,重复一次,再读取一个字串,把读取的数字和字串用来做个运算。至于怎么提示、如何读取输入、如何运算输入内容,可能会有种种不同的方式,那要看Interpreter具体是怎么做的了。好了,现在我们看看如何用fold来运算prg:fold需要两个入参数:r:A=>B,一个在运算终止Return状态时运行的函数,另一个是s:S[Free[S,A]]=>B,这个函数在Suspend状态时运算入参数ADT:

1 def runQuiz[A](p: Free[Quiz,A]): Unit= p.fold(_ => (), {
2   case Question(q,f) => {
3      println(q)
4      runQuiz(f(readLine))
5   }
6   case Answer(a,n) => println(a)
7 }) 

注意runQuiz是个递归函数。在Suspend Question状态下,运算f(readLine)产生下一个运算。在这个函数里我们赋予了提示、读取正真的意义,它们都是通过IO操作println,readLine实现的。

1 object main extends App {
2 import freeRun._
3 import qz._
4 runQuiz(prg)
5 }

运行结果:

The first number is:
3
The second number is:
8
The operation is:
mul
my answer is: 24

结果正是我们期待的。但这个fold方法每调用一次只运算一个ADT,所以使用了递归算法连续约化Suspend直到Return。递归算法很容易造成堆栈溢出异常,不安全。下一个试试foldMap。foldMap使用了Monad.bind连续通过高阶类型转换(natural transformation)将ADT转换成运行指令,并在转换过程中实施运算:

1 object QuizConsole extends (Quiz ~> Id) {
 2   import Quiz._
 3   def apply[A](qz: Quiz[A]): Id[A] = qz match {
 4     case Question(a,f) => {
 5       println(a)
 6       f(readLine)
 7     }
 8     case Answer(a,n) => println(a);n
 9   }
10 }
11 //运行foldMap
12 prg.foldMap(QuizConsole)
13 //结果一致

上面的natural transformation是把Quiz类型转成Id类型。Id[A]=A,所以高阶类型Quiz可以被转换成基本类型Unit(println返回Unit)。这个例子同样用IO函数来实现AST功能。我们也可以用一个模拟的输入输出方式来测试AST功能,也就是用另一个Interpreter来运算AST,我们可以用Map[String,String]来模拟输入输出环境:

 1 type Tester[A] = Map[String, String] => (List[String], A)
 2 object QuizTester extends (Quiz ~> Tester) {
 3    def apply[A](qa: Quiz[A]): Tester[A] = qa match {
 4      case Question(q,f) => m => (List(),f(m(q)))
 5      case Answer(a,n) => m => (List(a),n)
 6    }
 7 }
 8 implicit object testerMonad extends Monad[Tester] {
 9   def point[A](a: => A) = _ => (List(),a)
10   def bind[A,B](ta: Tester[A])(f: A => Tester[B]): Tester[B] =
11     m => {
12       val (o1,a) = ta(m)
13       val (o2,b) = f(a)(m)
14       (o1 ++ o2, b)
15     }
16 }

Tester必须是个Monad,所以我们必须提供隐式对象testerMonad。看看运算结果:

1 val m = Map(
2     "The first number is:" -> "8",
3     "The second number is:" -> "3",
4     "The operation is:" -> "Sub"
5 )
6 println(prg.foldMap(QuizTester).apply(m))
7 //(List(my answer is: 5),())

foldRun通过入参数f:(B,S[Free[S,A]])=>(B,Free[S,A])支持状态跟踪,入参数b:B是状态初始值。我们先实现这个f函数:

 1 type FreeQuiz[A] = Free[Quiz,A]
 2 def quizst(track: List[String], prg: Quiz[FreeQuiz[Unit]]): (List[String], FreeQuiz[Unit]) =
 3   prg match {
 4     case Question(q,f) => {
 5       println(q)
 6       val input = readLine
 7       (q+input :: track, f(input))
 8     }
 9     case Answer(a,n) => println(a); (a :: track, n)
10   }

运行foldRun的结果如下:

println(prg.foldRun(List[String]())(quizst)._1)
The first number is:
2
The second number is:
4
The operation is:
Mul
my answer is: 8
List(my answer is: 8, The operation is:Mul, The second number is:4, The first number is:2)

下一个是runM了,它的入参数就是一个S[_]到M[_]的转换函数:f: S[Free[S,A]]=>M[Free[S,A]]。我们先实现了这个f函数:

1 type FreeQuiz[A] = Free[Quiz,A]
2 def runquiz[A](prg: Quiz[FreeQuiz[A]]): Id[FreeQuiz[A]] =
3   prg match {
4   case Question(q,f) => {
5    println(q)
6    f(readLine)
7   }
8   case Answer(a,n) => println(a); n
9 }

测试运行runM:

prg.runM(run quiz)
The first number is:
4
The second number is:
2
The operation is:
Mul
my answer is: 8

我们曾经介绍过有些F[_]是无法实现map函数的,因此无法成为Functor,如以下ADT:

1 sealed trait Calc[+A]
 2 object Calc {
 3   case class Push(value: Int) extends Calc[Unit]
 4   case class Add() extends Calc[Unit]
 5   case class Mul() extends Calc[Unit]
 6   case class Div() extends Calc[Unit]
 7   case class Sub() extends Calc[Unit]
 8   implicit def calcToFree[A](ca: Calc[A]) = Free.liftFC(ca)
 9 }
10 import Calc._
11 val ast = for {
12   _ <- Push(23)
13   _ <- Push(3)
14   _ <- Add()
15   _ <- Push(5)
16   _ <- Mul()
17 } yield ()                                        //> ast  : scalaz.Free[[x]scalaz.Coyoneda[Exercises.interact.Calc,x],Unit] = Gosub()

从Calc无法获取B类型值,所以无法实现Calc.map,因而Calc无法成为Functor。runFC就是专门为运算Calc这样的非Functor高阶类型值的。runFC需要一个FreeC[S,A]类型入参数:

/** A free monad over the free functor generated by `S` */
  type FreeC[S[_], A] = Free[({type f[x] = Coyoneda[S, x]})#f, A]
}

可以得出runFC是专门为Coyoneda设计的。Coyoneda可以替代Calc[A],又是一个Functor,所以可以用Free产生Calc类型的Monad。我们先把Interpreter实现了:

1 type Stack = List[Int]
 2 type StackState[A] = State[Stack,A]
 3 object CalcStack extends (Calc ~> StackState) {
 4   def apply[A](ca: Calc[A]): StackState[A] = ca match {
 5     case Push(v) => State((s: Stack) => (v :: s, ()))
 6     case Add() => State((s: Stack) => {
 7       val a :: b :: t = s
 8       ((a+b) :: t,())
 9     })
10     case Mul() => State((s: Stack) => {
11       val a :: b :: t = s
12       ((a * b) :: t, ())
13     })
14     case Div() => State((s: Stack) => {
15       val a :: b :: t = s
16       ((a / b) :: t,())
17     })
18     case Sub() => State((s: Stack) => {
19       val a :: b :: t = s
20       ((a - b) :: s, ())
21     })
22   }
23 }

这个Interpreter用的是Stack内元素操作的运算方式。用runFC对ast运算的结果:

println(Free.runFC(ast)(CalcStack).apply(List[Int]()))
//(List(130),())

以上示范了针对任何抽象的Monadic Programm,我们如何通过各种Interpreter的具体实现方式来确定程序功能的。

时间: 2024-10-28 12:24:49

Scalaz(34)- Free :算法-Interpretation的相关文章

asp.NET 脏字过滤算法 修改版_实用技巧

旧的算法是简单对每一个脏字调用一遍 string.replace,当然是用了StringBuilder.http://www.jb51.net/article/20575.htm.在我这里测试的时候,RegEx要快一倍左右.但是还是不太满意,应为我们网站上脏字过滤用的相当多,经过一番思考后,自己做了一个算法.在自己的机器上测试了一下,使用原文中的脏字库,0x19c的字符串长度,1000次循环,文本查找耗时1933.47ms,RegEx用了1216.719ms,而我的算法只用了34.125ms.

设计模式之禅之设计模式-建造者模式

一:建造者模式的定义        --->建造者模式(Builder Pattern)也叫做生成器模式,其定义如下:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示        ● Product产品类                通常是实现了模板方法模式,也就是有模板方法和基本方法,这个参考模板方法模式.例子中的BenzModel和BMWModel就属于产品类.        ● Builder抽象建造者                规范产品的组建,一般是由子类

Scalaz(31)- Free :自由数据结构-算式和算法的关注分离

我们可以通过自由数据结构(Free Structure)实现对程序的算式和算法分离关注(separation of concern).算式(Abstract Syntax Tree, AST)即运算表达式,是对程序功能的描述.算法则是程序的具体运算方式(Interpreter),它赋予了算式意义.下面我们先用一个例子简单解释何为算式.算法: 用一个简单的表达式 1+2+3,这个表达式同时包含了算式和算法:运算表达式是 a Op b Op c, 算法是:Int加法,a,b,c为Int, oP为In

新算法能将网页加载速度提高 34%

MIT计算机科学和人工智能实验室与哈佛的研究人员开发了名为Polaris的 算法,能减少34%总页面加载时间.当我们访问一个网站,首先是在浏览器地址栏输入域名,DNS服务器将域名转换成托管网站的服务器IP地址,然后浏览器 会下载网站的索引文件,一个HTML页面.在HTML页面内,网站源代码会加载以CSS和JS文件.图像.Flash等形式的不同资源,每个资源都有独立 的网络请求,建立网络请求所需的时间是导致网页加载缓慢的主要原因.为了解决这个问题,Polaris框架为每个页面绘制一个依赖图,以最优

航班延误后如何缩短恢复时间?阿里云联手厦门航空34万大奖征集最有效智能算法

因航空管制.恶劣天气等因素导致航班不能正常运行时,如何迅速恢复航线.保障乘客出行?这对航空公司而言是相当困难的问题.目前,当航班遭遇以上特殊情况影响时,需要依靠人工来统计航班信息,往往6个小时以后才能出现航班调整方案.对于这一问题,目前并没有通用的技术,此前国际上一些大型航空软件公司提供的技术方案,在国内实施的效果也并不太理想. 6月10日,在云栖大会·上海峰会上,阿里云天池平台联合厦门航空启动"智慧航空AI大赛".大赛向全球发出邀请,聚集全球工程师的智能算法,以解决当航班遭遇雨雪.台

(算法导论习题解exercise2.3-4)递归版插入排序

没什么可以多说的,直接看代码吧: #include <stdio.h> void display(int array[], int size) { int i; for (i = 0; i < size; ++i) { printf("%d ", array[i]); } printf("\n"); } void insert_sort(int array[], int size) { if (size > 1) { insert_sort(

Scalaz(33)- Free :算式-Monadic Programming

  在任何模式的编程过程中都无法避免副作用的产生.我们可以用F[A]这种类型模拟FP的运算指令:A是可能产生副作用的运算,F[_]是个代数数据类型ADT(Algebraic Data Type),可以实现函数组合(functional composition),我们可以不用理会A,先用F[_]来组合形成描述功能的抽象程序AST(Abstract Syntax Tree),对A的运算可以分开另一个过程去实现,而且可以有多种的运算实现方式,这样就达到了算式AST(Monadic Programmin

Scalaz(36)- Free :实践-Free In Action - 实用体验

 在上面几期讨论中我们连续介绍了Free Monad.因为FP是纯函数编程,也既是纯函数的组合集成,要求把纯代码和副作用代码可以分离开来.Free Monad的程序描述(AST)和程序实现(Interpretation)关注分离(separation of concern)模式恰恰能满足FP要求.我们可以用一些代数数据类型(ADT Algebraic Data Type)来模拟功能,再把这些ADT组合起来形成AST(Abstract Syntax Tree).AST既是对程序功能的描述,它的组成

Scalaz(38)- Free :Coproduct-Monadic语句组合

 很多函数式编程爱好者都把FP称为Monadic Programming,意思是用Monad进行编程.我想FP作为一种比较成熟的编程模式,应该有一套比较规范的操作模式吧.因为Free能把任何F[A]升格成Monad,所以Free的算式(AST).算法(Interpreter)关注分离(separation of concern)模式应该可以成为一种规范的FP编程模式.我们在前面的几篇讨论中都涉及了一些AST的设计和运算,但都是一些功能单一,离散的例子.如果希望通过Free获取一个完整可用的程序,