泛函编程(32)-泛函IO:IO Monad

  • 时间:
  • 浏览:2
  • 来源:uu快3下载网站_uu快3开户二维码

大伙儿 也可不都可以 把副作用变成对List进行读写:

1、3个纯函数:A => D, 这里D所以我3个功能描述表达式

大伙儿 中间的简版IO类型只代表输出类型(output type)。Input类型前要3个存放输入值的变量。在泛函编程模式里变量是用类型参数代表的:

现在大伙儿 可不都可以 进入Interpreter编程了:

在以上的讨论过程中大伙儿 得出了另3个的结论:F类型代表了IO类型的Interpreter,大伙儿 不前要知道它到底产生副作用算不算为什么我么我让如可产生。大伙儿 用F类型来把副作用的使用推延到F类型的实例。大伙儿 的IO进程所以我对IO算法的描述。如可运算IO值包括如可使用副作用则在运算Interpreter时才体现。

你你你这个 IO类型把纯代码与副作用代码分开你你这个IO运算清况 :IO运算可算不算3个纯函数值,为什么我么我让是3个内部人员副作用运算请求。你你你这个 External类型定义了内部人员副作用运算法律最好的办法,它决定了大伙儿 进程能获得哪此样的内部人员副作用运算。你你你这个 External[I]就像3个表达式,但不可不都可以 用内部人员运算IO的进程来运算它。cont函数是个接续函数,它决定了获取External[I]运算结果后接着该做些哪此。

如上所示,任何副作用都可不都可以 被倒进Delay。为什么我么我让大伙儿 希望更好控制使用外界影响,可不都可以 把External的选项作为IO的类参数:

1 trait IO[F[_],+A] {}
2 case class Pure[F[_],+A](get: A) extends IO[F,A]
3 case class Request[F[_],I,+A](expr: F[I], cont: I => IO[F,A]) extends IO[F,A]

泛函模式的IO编程所以我把IO功能表达和IO副作用产生分开设计:IO功能描述使用基于IO Monad的Monadic编程语言,充分利用函数组合进行。而产生副作用的IO实现则推延到独立的Interpreter每种。当然,Interpreter就有为什么我么我让继续分解,把产生副作用代码再抽离向外推延,另3个大伙儿 还可不都可以 对Interpreter的纯代码核心进行函数组合。

现在大伙儿 可不都可以 明确分辨3个运算中的纯函数和副作用函数。为什么我么我让大伙儿 还无法控制External类型的行为。External[I]可不都可以 代表3个简单的推延值,如下:

大伙儿 用run来对IO值进行计算。在中间大伙儿 为什么我么我让实现了map和flatMap函数,所以你你你这个 IO类型所以我个Monad。看下面:

1 object IO extends Monad[IO] {
2     def unit[A](a: A) = new IO[A] {def run = a}
3     def flatMap[A,B](ma: IO[A])(f: A => IO[B]) = ma flatMap f
4     def map[A,B](ma: IO[A])(f: A => B) = ma map f
5     def apply[A](a: A) = unit(a)  //IO构建器,可不都可以

实现  IO {...}
6 }

为什么我么我让大伙儿 前要采用无独占(Non-blocking)读写副作用的话可不都可以 另3个改写Interpreter:

大伙儿 可不都可以 用Free Monad的特性替代IO类型特性,另3个大伙儿 就可不都可以 用Monadic编程语言来描述IO进程。至于实际的IO副作用如可,大伙儿 只知道产生副作用的Interpret进程是个Monad,其它一无所知。

假使 大伙儿 在Interpret进程里把GetLine,PutLine对应到InLog,OutLog3个List的读写。

你你你这个 foldMap所以我3个IO进程运算函数。为什么我么我让它是3个循环计算,所以通过resume函数引入Trampoline尾递归计算法律最好的办法来保证补救StackOverflow什么的问题地处。foldMap函数将IO描述语言F解译成为什么我么我让产生副作用的G语言。在解译的过程中逐步用flatMap运行非纯代码。

可不都可以 看出你你你这个 run函数是个递归算法:先计算F值为什么我么我让再递归调用run运算所产生的IO值。

在泛函编程中大伙儿 会持续进行你你你这个 函数分解(factoring),把蕴含副作用的代码分解提取出来向外推形成3个副作用代码层。你你你这个 非纯函数形成的代码层内部人员则是经分解形成的纯代码核心。最后大伙儿 到达了哪此皮层上为什么我么我让无可分解的非纯函数如:println,它的类型是String => Unit, 接下去大伙儿 应该怎办呢?

有了F类型的Monad实例,函数runM现在能运算IO类型的值了。

2、3个带副作用的非纯函数: D => B, 它可不都可以 被视为D的解译器(interpreter),把描述解译成有副作用的指令

大伙儿 现在可不都可以 创建3个F类型的实例为什么我么我让运算IO:

现在通过IO[Console,A]大伙儿 获得了不可不都可以 对键盘显示屏进行读写的副作用。当然,大伙儿 还可不都可以 定义文件、数据库、网络读写哪此IO能力的F类型。所以大伙儿 通过定义F来规范使用副作用。注意,即使在Console类型大伙儿 也无法获知副作用算不算的确产生,这每种是由F类型的Interpreter在运算IO进程时才确定的。这不又是Free Monad分开独立考虑(separation of concern)的Interpreter每种嘛。这是大伙儿 可不都可以 把这每种延后分开考虑。

大伙儿 说过:运算IO值所以我把IO进程语言逐句翻译成产生副作用的Interpreter语言你你你这个 过程。在以上例子里大伙儿 采用了Id Monad作为Interpreter语言。Id Monad的flatMap不做任何事情,所以IO进程被直接对应到基本IO函数readLine, println上了。

大伙儿 先看看如可计算你你你这个 IO类型的值:

大伙儿 再来看看你你你这个 IO类型:IO[A] { def run: A },从类型款式来看大伙儿 只知道IO[A]类型值是个延后值,为什么我么我让A值是通过调用函数run取得的。实际清况 是run在运算A值时run函数里的纯代码向进程外的环境提请某些运算要求如输入(readLine),为什么我么我让把结果传递到另外某些纯代码;为什么我么我让哪此纯代码有为什么我么我让又向外提请。大伙儿 根本无法确定副作用是在那个环节产生的。这麼可不都可以 确定,你你你这个 IO类型无法完整性地表达IO运算。大伙儿 必需对IO类型进行重新定义:

1 trait IO[A] {def run: A}
2 case class Pure[+A](a: A) extends IO[A]
3 case class Request[Extenal[_],I,A](expr: Extenal[I], cont: I => IO[A]) extends IO[A]

作为IO算法,首先前要注意补救的所以我递归算法产生的堆栈溢出什么的问题。运算IO值的runM是个递归算法,另3个们前要保证大慨它是3个尾递归算法。当然,大伙儿 前面讨论的Trampoline类型是最佳确定。大伙儿 可不都可以 比较一下IO和Trampoline类型特性:

实际上通过增加3个新的数据类型IO大伙儿 甚至可不都可以 对println进行分解:

你你你这个 例子看起来好像某些幼稚,但它示范了泛函编程的函数分解原理:大伙儿 并这麼改变进程的功能,所以我对进程代码进行了分解。把进程分解成更细的函数。实际上任何3个蕴含副作用的函数内就有有纯函数听候大伙儿 去分解。用你你这个代数关系表达所以我:任何3个函数 A => B 都可不都可以 被分解成:

 为什么我么我让泛函编程非常重视函数组合(function composition),任何蕴含副作用(side effect)的函数都无法实现函数组合,所以前要把蕴含外界影响(effectful)副作用不纯代码(impure code)函数中的纯代码每种(pure code)抽离出来形成独立的另3个纯函数。大伙儿 通过代码抽离把不纯代码逐步抽离向外推并在进程里形成3个纯代码核心(pure core)。另3个大伙儿 就可不都可以 顺利地在你你你这个 纯代码核心中实现函数组合。IO Monad所以我泛函编程补救副作用代码的你你这个手段。大伙儿 先用个例子来示范副作用抽离:

这里涉及到某些大的概念:编写IO进程和运算IO值是相互分离的过程(separation of concern)。大伙儿 的IO进程用一系列表达式描述了要求的IO功能,而IO interpreter实现哪此功能的法律最好的办法可算不算多样的,包括:外设读写,文件、数据库读写及并行读取等等。如可实现哪此IO功能与IO进程编写无任何关系。

大伙儿 把分数比较代码winner分离了出来。大伙儿 还可不都可以 继续分解:printWinner也可不都可以 被认为做了两件事:先合成了一根信息,为什么我么我让打印信息:

实际上你你你这个 IO类型是个Monad,为什么我么我让大伙儿 可不都可以 实现它的unit和flatMap函数:

 1 trait IO[F[_],A] {
 2  def unit(a: A) = Pure(a)
 3  def flatMap[B](f: A => IO[F,B]): IO[F,B] = this match {
 4      case Pure(a) => f(a)
 5 //     case Request(expr,cont) => Request(expr, cont andThen (_ flatMap f))
 6   case Request(expr,cont) => Request(expr, (x: Any) => cont(x) flatMap f)
 7  }
 8  def map[B](f: A => B): IO[F,B] = flatMap(a => Pure(f(a)))
 9 }
10 case class Pure[F[_],A](get: A) extends IO[F,A]
11 case class Request[F[_],I,A](expr: F[I], cont: I => IO[F,A]) extends IO[F,A]

从以上的讨论大伙儿 得出:IO类型可不都可以 用Free类型代替,另3个大伙儿 可不都可以 充分利用Free Monad的Monadic编程语言编写IO进程,大伙儿 又可不都可以 分开考虑编写为什么我么我让产生IO副作用的Interpreter。

既然IO类型是个Monad类型,这麼大伙儿 就可不都可以 使用monadic语言编程了:

1 def ReadLine: IO[String] = IO { readLine }
2 def PrintLine(msg: String): IO[Unit] = IO { println(msg) }
3 def fahrenheitToCelsius(f: Double): Double =
4   (f -32) * 5.0 / 9.0
5 def converter: IO[Unit] = for {
6     _ <- PrintLine("Enter a temperature in degrees fahrenheit:")
7     d <- ReadLine.map(_.toDouble)
8     _ <- PrintLine(fahrenheitToCelsius(d).toString)
9 } yield ()

很明显,declareWinner是个蕴含副作用的函数。它做了两件事:先对比3个Player的分数为什么我么我让打印分数较大的Player。这里打印可不都可以 说是一项蕴含外作用的函数,大伙儿 试着把它分离出来:

现在,有了你你你这个 IO类型,大伙儿 可不都可以 放心地用函数组合的泛函编程法律最好的办法围绕着你你你这个 IO类型来编写IO进程,为什么我么我让大伙儿 知道通过你你你这个 IO类型大伙儿 把副作用的产生推延到IO进程之外的IO解译器里,而IO编程与解译器是3个人及 所有独立的进程。

大伙儿 所以我把External再加了F,为什么我么我让把它倒进IO类型参数。现在大伙儿 可不都可以 通过定义不同的F类型来获取不同的副作用,例如:

1 trait Console[A]
2 case object ReadLine extends Console[Option[String]]
3 case class PrintLine(msg: String) extends Console[Unit]

现在函数printWinner为什么我么我让变成了纯函数,它返回了3个IO值:你你你这个 IO值所以我对3个副作用的描述但并这麼运行它。不可不都可以 你你你这个 IO类型的解译器(interpreter)在运算你你你这个 IO值时才会真正产生相应的副作用。