博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Scalaz(33)- Free :算式-Monadic Programming
阅读量:4879 次
发布时间:2019-06-11

本文共 4474 字,大约阅读时间需要 14 分钟。

  在任何模式的编程过程中都无法避免副作用的产生。我们可以用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 / snum10         case 'S' => fnum - snum11       }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 methods14   def askNumber(q: String) = Question(q, (inputString => inputString.toInt))  //_.toInt15   def askOperator(q: String) = Question(q, (inputString => inputString.head.toUpper.toChar)) //_.head.toUpper.toChar16   def answer(fnum: Int, snum: Int, opr: Char) = {17     def result =18       opr match {19         case 'A' => fnum + snum20         case 'M' => fnum * snum21         case 'D' => fnum / snum22         case 'S' => fnum - snum23       }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的工作。这个例子不就真正体现了算式算法的关注分离了的精髓嘛。

 

 

 

 

转载于:https://www.cnblogs.com/tiger-xc/p/5295359.html

你可能感兴趣的文章
html让没有宽高限制的图片居中
查看>>
phpStudy中起用lua脚本
查看>>
钉钉开发系列(八)二维码扫描登录的实现
查看>>
android studio
查看>>
Linux简介和安装
查看>>
微信公众平台开发(86) 获取用户基本信息
查看>>
C#开发之反射的简单使用
查看>>
MSSQL重拾记录
查看>>
[转] VS2015中跑OpenGL红宝书第八版的第一章示例代码,运行
查看>>
shell编程笔记(1)
查看>>
Python学习(四)数据结构 —— str
查看>>
AndroidStudio检测不到genymotion虚拟设备
查看>>
volatile关键字
查看>>
Firebug入门指南
查看>>
Kotlin偏好设置
查看>>
PhpStorm一次性折叠所有函数或者方法
查看>>
[HEOI2014]大工程
查看>>
Windows 下 Oracle 10g 手工创建数据库
查看>>
《设计模式之禅》学习笔记(十二)
查看>>
#C++PrimerPlus# Chapter10_Exersice8_v1.0
查看>>