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 Programming)、算法(Interpretation)的所谓关注分离(separation of concern)目的。在前面的讨论中我们介绍过:我们可以把任何F[A]升格成Monad,而Monad具备最完善的函数组合性能,特别是它支持for-comprehension这种表达方式。我们可以在for-comprehension框架里进行我们熟悉的行令编程(imperative programming),可以使程序意思表达更加显而易见。

下面我们来做一个简单的示范:模拟一个互动智力算数测试(math quiz):在系统提示下,用户输入第一个数字、再输入第二个数字、再输入操作符号、系统输出算数操作结果。我们可以设计ADT如下:

1 sealed trait Quiz[+Next]
2 case class Question[Next](que: String, n: String => Next) extends Quiz[Next]
3 case class Answer[Next](ans: String, n: Next) extends Quiz[Next]

Quiz类型可能属于Question或Answer。Question需要读取一个String类型输入,由于实际需要的可能是一个Int或者是Char,在获取输入后还要进行下一步类型转换(map),所以还必须把一个转换函数String=>Next存放入Question结构。Answer则不需要任何输入,所以我们会把()作为Next的值存入Answer结构。

我们可以map over Next类型获取Quiz的Functor实例:

1   implicit object QFunctor extends Functor[Quiz] {
2     def map[A,B](qa: Quiz[A])(f: A => B): Quiz[B] =
3       qa match {
4          case q: Question[A] => Question(q.que, q.n andThen f)
5          case Answer(a,n) => Answer(a,f(n))
6       }
7   }

从case q: Question[A]可以看出来:map over Next实际上是连续运算(andThen)。

我们再来几个操作帮助方法:

 1 //操作帮助方法helper methods
 2   def askNumber(q: String) = Question(q, (inputString => inputString.toInt))  //_.toInt
 3   def askOperator(q: String) = Question(q, (inputString => inputString.head.toUpper.toChar))
 4   def answer(fnum: Int, snum: Int, opr: Char) = {
 5     def result =
 6       opr match {
 7         case 'A' => fnum + snum
 8         case 'M' => fnum * snum
 9         case 'D' => fnum / snum
10         case 'S' => fnum - snum
11       }
12     Answer("my answer is: " + result.toString,())
13   }

我们现在可以这样编写AST了: 

1 import Quiz._
2 val prg = for {
3  fn <- askNumber("The first number is:")
4  sn <- askNumber("The second number is:")
5  op <- askOperator("The operation is:")
6  _ <- answer(fn,sn,op)
7 } yield()                                         //> prg  : scalaz.Free[Exercises.interact.Quiz,Unit] = Gosub()

但是,askNumber,askOperator及answer这几个操作函数都返回了Quiz类型,而Quiz类型不是Monad,不支持for-comprehension。我们可以用个隐式转换把所有Quiz[A]升格成Free[Quiz,A]:

1   implicit def quizToFree[A](qz: Quiz[A]): Free[Quiz,A] = Free.liftF(qz)

这个示范完整的源代码如下:

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

再看看下面的例子。试着猜测程序的作用:

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

从上面的AST表达方式可以估计到这是一个对Int进行加减乘除的计算器,应该是先通过push把操作对象存入一个Stack。然后对Stack内部的数字进行计算操作。具体是如何实现的,在这个阶段无需知道,这应该是Interpreter的工作。这个例子不就真正体现了算式算法的关注分离了的精髓嘛。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏生信宝典

Pandas,让Python像R一样处理数据,但快

What is pandas Pandas是python中用于处理矩阵样数据的功能强大的包,提供了R中的dataframe和vector的操作,使得我们在使用p...

1955
来自专栏函数式编程语言及工具

Scalaz(34)- Free :算法-Interpretation

我们说过自由数据结构(free structures)是表达数据类型的最简单结构。List[A]是个数据结构,它是生成A类型Monoid的最简单结构,因为我们...

1986
来自专栏数据结构与算法

1570. [POJ3461]乌力波

★☆   输入文件:oulipo.in   输出文件:oulipo.out 简单对比 时间限制:1 s   内存限制:256 MB 【题目描述】 法国作家乔治·...

2927
来自专栏章鱼的慢慢技术路

2013年第四届蓝桥杯C/C++B组省赛题目解析

1443
来自专栏转载gongluck的CSDN博客

FLV文件格式官方规范详解

——如果要学习一个新的知识点,官方手册可能是最快的途径。查看网上其他人的总结也许入门更快,但是要准确,深入,完整,还是要看官方手册。 以下内容来自对官方文档Vi...

4215
来自专栏生信宝典

Pandas使用 (一)

What is pandas Pandas是python中用于处理矩阵样数据的功能强大的包,提供了R中的dataframe和vector的操作,使得我们在使用p...

2529
来自专栏C语言及其他语言

[每日一题]自定义函数与宏定义

前面,我们既一起研究了自定义函数的题,有看了宏定义的题,下面这题刚好就是要用自定义函数和宏定义解题的,帮大家巩固知识点 题目描述 分别用函数和带参的宏,从三个...

2645
来自专栏CreateAMind

条件GAN重大改进!cGANs with Projection Discriminator

1671
来自专栏帮你学MatLab

《Experiment with MATLAB》读书笔记(二)

读书笔记(二) 这是第二部分函数与绘图 将代码复制到m文件即可运行 函数部分需新建m文件保存 %% 函数 % 变量在用之前先赋0站位 % 计数或者...

2717
来自专栏ascii0x03的安全笔记

【C】用C语言提取bmp图片像素,并进行K-means聚类分析——容易遇到的问题

关于bmp图片的格式,网上有很多文章,具体可以参考百度百科,也有例子程序。这里只提要注意的问题。 (1)结构体定义问题:首先按照百度百科介绍的定义了结构体,但是...

4636

扫码关注云+社区