泛函编程(33)-泛函IO:Free Functor - Coyoneda
在前几期讨论中我们终于推导出了Free Monad。这是一个Monad工厂,它可以把任何F[A]变成Monad。可惜的是它对F[A]是有所要求的:F必须是个Functor。Free Monad由此被称为由Functor F 产生的Monad。F必须是Functor,这个门槛使我们在使用Free Monad时很不方便。举个前面讨论过的例子:
trait Console[A]
case object GetLine extends Console[String]
case class PutLine(line: String) extends Console[Unit]
我们想用Free Monad把Console[A]变成Monad: Free[Console,A],但我们必须先得到Console的Functor实例:
implicit val consoleFunctor = new Functor[Console] {
def map[A,B](ca: Console[A])(f: A => B): Console[B] = ca match {
case GetLine => ?????
case PutLine(l) => ????
}
}
讲老实话,我到现在还没能想出如何实现这个map函数。除非把Console类型修改一下,这个可以参考前面讨论中的代码。
现在的问题是如果能有个什么方法把F[A]变成Functor,就像Free Monad那样有个Free Functor就好了。范畴学中Yoneda lemma结论中的Coyoneda就是一个Free Functor。
Yoneda lemma是这样推论的:如果我们有个这样的函数定义:def map[B](f: A => B): F[B],那我们就肯定能得出F[A]值,因为我们只需要把一个恒等函数当作f就能得到F[A]。反过来推论:如果我们有个F[A],F是任何Functor,A是任何类型,我们同样可以得出以上的map函数。我们可以用个类型来表示:
trait Yoneda[F[_],A] {
def map[B](f: A => B): F[B]
}
当然,这也意味着如果:有个类型B,一个函数(B => A),A是任意类型,一个F[B],F是任意Functor,我们肯定能得出F[A]:因为我们只要把(B => A)和F[B]传入map:
map(fb: F[B])(f: B => A): F[A]。
我们同样可以用一个类型来表示:
trait Coyoneda[F[_],A] { coyo =>
type I
def fi: F[I]
def k(i: I): A
}
在下面我们可以证明F[A]同等Coyoneda[F,A],而Coyoneda是个Functor。我们只需将F[A]升格(lift)到Coyoneda就能得到一个Free Functor了。
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
object Functor {
def apply[F[_]: Functor]: Functor[F] = implicitly[Functor[F]]
}
trait Monad[M[_]] {
def unit[A](a: A): M[A]
def flatMap[A,B](ma: M[A])(f: A => M[B]): M[B]
def map[A,B](ma: M[A])(f: A => B) = flatMap(ma)(a => unit(f(a)))
}
object Monad {
def apply[M[_]: Monad]: Monad[M] = implicitly[Monad[M]]
}
trait Yoneda[F[_],A] { yo =>
def apply[B](f: A => B): F[B]
def run: F[A] = apply(a => a) //无需Functor实例就可以将Yoneda转变成F[A]
def toCoyoneda: Coyoneda[F,A] = new Coyoneda[F,A] { //转Coyoneda无需Functor
type I = A
def fi = yo.run
def k(i: A) = i
}
def map[B](f: A => B): Yoneda[F,B] = new Yoneda[F,B] { //纯粹的函数组合 map fusion
def apply[C](g: B => C): F[C] = yo( f andThen g)
}
}
trait Coyoneda[F[_],A] { coyo =>
type I
def fi: F[I]
def k(i: I): A
def run(implicit F: Functor[F]): F[A] = //Coyoneda转F需要F Functor实例
F.map(fi)(k)
def toYoneda(implicit F: Functor[F]): Yoneda[F,A] = new Yoneda[F,A] { //转Yoneda需要Functor
def apply[B](f: A => B): F[B] = F.map(fi)(k _ andThen f)
}
def map[B](f: A => B): Coyoneda[F,B] = new Coyoneda[F,B] {
type I = coyo.I
def fi = coyo.fi
def k(i: I) = f(coyo k i)
}
}
object Yoneda {
def apply[F[_]: Functor,A](fa: F[A]) = new Yoneda[F,A] { //F转Yoneda需要Functor
def apply[B](f: A => B): F[B] = Functor[F].map(fa)(f)
}
implicit def yonedaFunctor[F[_]] = new Functor[({type l[x] = Yoneda[F,x]})#l] {
def map[A,B](ya: Yoneda[F,A])(f: A => B) = ya map f
}
}
object Coyoneda {
def apply[F[_],A](fa: F[A]): Coyoneda[F,A] = new Coyoneda[F,A] {
type I = A //把F[A]升格成Coyoneda, F无须为Functor
def fi = fa
def k(a: A) = a
}
implicit def coyonedaFunctor[F[_]] = new Functor[({type l[x] = Coyoneda[F,x]})#l] {
def map[A,B](ca: Coyoneda[F,A])(f: A => B) = ca map f //Coyoneda本身就是Functor
}
}
以上值得注意的是:F[A]可以直接升格等于Coyoneda,而Coyoneda是个Functor。换句话说我们把F[A]升格到Coyoneda就可以当Functor来用了。
我们的目的是把任何F[A]变成Free Monad,那么我们就需要有一个用Coyoneda产生的Free:
trait Free[F[_],A] {
private case class FlatMap[B](a: Free[F,A], f: A => Free[F,B]) extends Free[F,B]
def unit(a: A): Free[F,A] = Return(a)
def flatMap[B](f: A => Free[F,B])(implicit F: Functor[F]): Free[F,B] = this match {
case Return(a) => f(a)
case Suspend(k) => Suspend(F.map(k)(a => a flatMap f))
case FlatMap(b,g) => FlatMap(b, g andThen (_ flatMap f))
}
def map[B](f: A => B)(implicit F: Functor[F]): Free[F,B] = flatMap(a => Return(f(a)))
def resume(implicit F: Functor[F]): Either[F[Free[F,A]],A] = this match {
case Return(a) => Right(a)
case Suspend(k) => Left(k)
case FlatMap(a,f) => a match {
case Return(b) => f(b).resume
case Suspend(k) => Left(F.map(k)(_ flatMap f))
case FlatMap(b,g) => FlatMap(b, g andThen (_ flatMap f)).resume
}
}
def foldMap[G[_]](f: (F ~> G))(implicit F: Functor[F], G: Monad[G]): G[A] = resume match {
case Right(a) => G.unit(a)
case Left(k) => G.flatMap(f(k))(_ foldMap f)
}
}
case class Return[F[_],A](a: A) extends Free[F,A]
case class Suspend[F[_],A](ffa: F[Free[F,A]]) extends Free[F,A]
object Free {
import scalaz.Unapply
/** A free monad over the free functor generated by `S` */
type FreeC[S[_], A] = Free[({type f[x] = Coyoneda[S, x]})#f, A]
/** Suspends a value within a functor in a single step. Monadic unit for a higher-order monad. */
def liftF[S[_], A](value: => S[A])(implicit S: Functor[S]): Free[S, A] =
Suspend(S.map(value)(Return[S, A]))
/** A version of `liftF` that infers the nested type constructor. */
def liftFU[MA](value: => MA)(implicit MA: Unapply[Functor, MA]): Free[MA.M, MA.A] =
liftF(MA(value))(MA.TC)
/** A free monad over a free functor of `S`. */
def liftFC[S[_], A](s: S[A]): FreeC[S, A] =
liftFU(Coyoneda(s))
/** 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)
})
}
我们把前面推导出来的Free搬过来。然后在Free companion object里增加了FreeC类型:
type FreeC[S[_],A] = Free[({type f[x] = Coyoneda[F,x]})#f, A]
这个可以说是一个由Coyoneda产生的Free。
现在我们要想办法把S[A]升格成FreeC:liftFC[S[_],A](s: S[A]): FreeC[S,A],这里需要先把S[A]升格成Coyoneda:Coyoneda(s)。
由于Coyoneda[S,A]是个多层嵌入类型。我们在liftFU函数中需要借用scalaz的Unapply类型来分解出Coyoneda, S[A]然后施用在liftF;
def liftF[S[_],A](sa: S[A])(implicit S: Functor[S]),这里的S就是Coyoneda。
Interpreter沿用了foldMap但是调整了转换源目标类型 Functor >>> Coyoneda。其它如Trampoline机制维持不变。
现在我们可以直接用任何F[A]来产生Free了。先试试上面的那个Console。这个Console不是个Functor:
trait Console[A]
case object GetLine extends Console[String]
case class PutLine(line: String) extends Console[Unit]
import Free._
implicit def liftConsole[A](ca: Console[A]): FreeC[Console,A] = liftFC(ca)
//> liftConsole: [A](ca: ch13.ex11.Console[A])ch13.ex11.Free.FreeC[ch13.ex11.Co
//| nsole,A]
for {
_ <- PutLine("What is your first name ?")
first <- GetLine
_ <- PutLine("What is your last name ?")
last <- GetLine
_ <- PutLine(s"Hello, $first $last !")
} yield () //> res0: ch13.ex11.Free[[x]ch13.ex11.Coyoneda[ch13.ex11.Console,x],Unit] = Sus
//| pend(ch13.ex11$Coyoneda$$anon$4@50f8360d)
可以使用Free的Monadic语言了。下面再试试Interpreter部分:
val ioprg = for {
_ <- PutLine("What is your first name ?")
first <- GetLine
_ <- PutLine("What is your last name ?")
last <- GetLine
_ <- PutLine(s"Hello, $first $last !")
} yield () //> ioprg : ch13.ex11.Free[[x]ch13.ex11.Coyoneda[ch13.ex11.Console,x],Unit] =
//| Suspend(ch13.ex11$Coyoneda$$anon$4@13c78c0b)
type Id[A] = A
implicit val idMonad = new Monad[Id] {
def unit[A](a: A) = a
def flatMap[A,B](fa: A)(f: A => B): B = f(fa)
} //> idMonad : ch13.ex11.Monad[ch13.ex11.Id] = ch13.ex11$$anonfun$main$1$$anon$
//| 10@12843fce
object RealConsole extends (Console ~> Id) {
def apply[A](ca: Console[A]): A = ca match {
case GetLine => readLine
case PutLine(l) => println(l)
}
}
Free.runFC(ioprg)(RealConsole) //> What is your first name ?/
case class State[S,A](runState: S => (A,S)) {
def map[B](f: A => B) = State[S,B](s => {
val (a1,s1) = runState(s)
(f(a1),s1)
})
def flatMap[B](f: A => State[S,B]) = State[S,B](s => {
val (a1,s1) = runState(s)
f(a1).runState(s1)
})
}
case class InOutLog(inLog: List[String], outLog: List[String])
type LogState[A] = State[InOutLog, A]
implicit val logStateMonad = new Monad[LogState] {
def unit[A](a: A) = State(s => (a, s))
def flatMap[A,B](sa: LogState[A])(f: A => LogState[B]) = sa flatMap f
} //> logStateMonad : ch13.ex11.Monad[ch13.ex11.LogState] = ch13.ex11$$anonfun$m
//| ain$1$$anon$11@3dd3bcd
object MockConsole extends(Console ~> LogState) {
def apply[A](c: Console[A]): LogState[A] = State(
s => (c,s) match {
case (GetLine, InOutLog(in,out)) => (in.head, InOutLog(in.tail, out))
case (PutLine(l), InOutLog(in,out)) => ((),InOutLog(in, l :: out))
})
}
val s = Free.runFC(ioprg)(MockConsole) //> s : ch13.ex11.LogState[Unit] = State(<function1>)
val ls = s.runState(InOutLog(List("Tiger","Chan"),List()))
//> ls : (Unit, ch13.ex11.InOutLog) = ((),InOutLog(List(),List(Hello, Tiger Ch
//| an !, What is your last name ?, What is your first name ?)))
泛函编程(33)-泛函IO:Free Functor - Coyoneda的更多相关文章
- 泛函编程(5)-数据结构(Functional Data Structures)
编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量 ...
- 泛函编程(32)-泛函IO:IO Monad
由于泛函编程非常重视函数组合(function composition),任何带有副作用(side effect)的函数都无法实现函数组合,所以必须把包含外界影响(effectful)副作用不纯代码( ...
- 泛函编程(38)-泛函Stream IO:IO Process in action
在前面的几节讨论里我们终于得出了一个概括又通用的IO Process类型Process[F[_],O].这个类型同时可以代表数据源(Source)和数据终端(Sink).在这节讨论里我们将针对Proc ...
- 泛函编程(36)-泛函Stream IO:IO数据源-IO Source & Sink
上期我们讨论了IO处理过程:Process[I,O].我们说Process就像电视信号盒子一样有输入端和输出端两头.Process之间可以用一个Process的输出端与另一个Process的输入端连接 ...
- 泛函编程(35)-泛函Stream IO:IO处理过程-IO Process
IO处理可以说是计算机技术的核心.不是吗?使用计算机的目的就是希望它对输入数据进行运算后向我们输出计算结果.所谓Stream IO简单来说就是对一串按序相同类型的输入数据进行处理后输出计算结果.输入数 ...
- 泛函编程(30)-泛函IO:Free Monad-Monad生产线
在上节我们介绍了Trampoline.它主要是为了解决堆栈溢出(StackOverflow)错误而设计的.Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法 ...
- 泛函编程(28)-粗俗浅解:Functor, Applicative, Monad
经过了一段时间的泛函编程讨论,始终没能实实在在的明确到底泛函编程有什么区别和特点:我是指在现实编程的情况下所谓的泛函编程到底如何特别.我们已经习惯了传统的行令式编程(imperative progra ...
- 泛函编程(27)-泛函编程模式-Monad Transformer
经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative, ...
- 泛函编程(25)-泛函数据类型-Monad-Applicative
上两期我们讨论了Monad.我们说Monad是个最有概括性(抽象性)的泛函数据类型,它可以覆盖绝大多数数据类型.任何数据类型只要能实现flatMap+unit这组Monad最基本组件函数就可以变成Mo ...
随机推荐
- instanceof, typeof, & Object.prototype.toString
/** * * @authors Your Name (you@example.org) * @date 2016-11-18 09:31:23 * @version $Id$ */instanceo ...
- 安卓学习进程(2)Android开发环境的搭建
本节将分为五个步骤来完成Android开发环境的部署. 第一步:安装JDK. 第二步:配置Windows上JDK的变量环境 . 第三步:下载安装Eclipse . 第四步:下载安装Androi ...
- 开发工程师面试的秘密( 整理自 Export C Programming )
开发工程师面试的秘密 因为打算转战linux平台,所以一直在配置自己喜欢的linux操作系统.同时在看那本<C 专家编程>,这本书主要是针对ANSI C 介绍的,所以和Linux(Unix ...
- echart饼状图使用,打发时间。
新公司,刚来几天,闲着没事,领导让我做些无关痛痒的活,优化报表统计!!!之前是用flash做的,现在要改成echart实现.好吧,之前没用过,抱着学习态度,研究了下.写点东西打发下时间,能帮到需要帮助 ...
- 深入理解javascript作用域系列第三篇——声明提升(hoisting)
× 目录 [1]变量 [2]函数 [3]优先 前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javasc ...
- ASP.NET MVC图片管理(上传,预览与显示)
先看看效果(下面gif动画制作有点大,5.71MB): 题外话:上面选择图片来源于Insus.NET的新浪微博:http://weibo.com/104325017 也是昨晚(2015-07-03)I ...
- 邻接矩阵无向图(二)之 C++详解
本章是通过C++实现邻接矩阵无向图. 目录 1. 邻接矩阵无向图的介绍 2. 邻接矩阵无向图的代码说明 3. 邻接矩阵无向图的完整源码 转载请注明出处:http://www.cnblogs.com/s ...
- 数据结构:JAVA_二叉数查找树基本实现(上)
数据结构:二叉数查找树基本实现(JAVA语言版) 1.写在前面 二叉查找树是一种能将链表插入的灵活性与有序数组查找的高效性结合在一起的一种数据结构. ..... 2.代码分解 2.1 对节点的结构定义 ...
- 简单登录实例Login
本人菜鸟~~学习过程中~~请求老大们指导!!谢谢!!! 从基础学习,坚持下去,每天进步一点点!! 1.首先要实现登录,通俗点总得有个登陆的样子吧,也就是人要有个脸面嘛!说做就做!自己属于菜鸟级别的,所 ...
- Mina工作原理分析
Mina是Apache社区维护的一个开源的高性能IO框架,在业界内久经考验,广为使用.Mina与后来兴起的高性能IO新贵Netty一样,都是韩国人Trustin Lee的大作,二者的设计理念是极为相似 ...