Scalaz(32)- Free :lift - Monad生产线

    在前面的讨论里我们提到自由数据结构就是产生某种类型的最简化结构,比如:free monoid, free monad, free category等等。我们也证明了List[A]是个free monoid。我们再看看free monad结构Free的定义:scalaz/Free.scala

/** A free operational monad for some functor `S`. Binding is done using the heap instead of the stack,
  * allowing tail-call elimination. */
sealed abstract class Free[S[_], A] {
...
  /** Return from the computation with the given value. */
  private[scalaz] case class Return[S[_], A](a: A) extends Free[S, A]

  /** Suspend the computation with the given suspension. */
  private[scalaz] case class Suspend[S[_], A](a: S[Free[S, A]]) extends Free[S, A]
...

我们在上一篇里证明过Free就是free monad,因为Free是个Monad而且它的结构是最简单的了:

1、Free[S[_],A]可以代表一个运算

2、case class Return是一个数据结构。Return(a:A)代表运算结束,运算结果a存放在结构中。另一个意义就是Monad.point(a:A),把一个任意A值a升格成Free

3、case class Suspend也是另一个数据结构。Suspend(a: S[Free[S,A]])代表把下一个运算存放在结构中。如果用Monad.join(a: F[F[A]])表示,那么里面的F[A]应该是个Free[S,A],这样我们才可能把运算结束结构Return[S,A](a:A)存放到Suspend中来代表下一步结束运算。

注意,Suspend(a: S[Free[S,A]])是个递归类型,S必须是个Functor,但不是任何Functor,而是map over Free[S,A]的Functor,也就是运算另一个Free[S,A]值。如果这个Free是Return则返回运算结果,如果是Suspend则继续递归运算。

简单来说Free是一个把Functor S[_]升格成Monad的产生器。我们可以用Free.liftF函数来把任何一个Functor升格成Monad。看看Free.liftF的函数款式就知道了:

  /** 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]))

liftF可以把一个S[A]升格成Free[S,A]。我们用个例子来证明:

1 package Exercises
 2 import scalaz._
 3 import Scalaz._
 4 import scala.language.higherKinds
 5 import scala.language.implicitConversions
 6 object freelift {
 7 trait Config[+A] {
 8   def get: A
 9 }
10 object Config {
11   def apply[A](a: A): Config[A] = new Config[A] { def get = a}
12   implicit val configFunctor = new Functor[Config] {
13      def map[A,B](ca: Config[A])(f: A => B) = Config[B](f(ca.get))
14   }
15 }
16
17 val freeConfig = Free.liftF(Config("hi config"))  //> freeConfig  : scalaz.Free[Exercises.freelift.Config,String] = Suspend(Exercises.freelift$Config$$anon$2@d70c109)

在上面的例子里Config是个运算A值的Functor。我们可以用Free.liftF把Config(String)升格成Free[Config,String]。实际上我们可以把这个必须是Functor的门槛取消,因为用Coyoneda就可以把任何F[A]拆解成Coyoneda[F,A],而Coyoneda天生是个Functor。我们先看个无法实现map函数的F[A]:

1 trait Interact[+A]  //Console交互
2 //println(prompt: String) then readLine 返回String
3 case class Ask(prompt: String) extends Interact[String]
4 //println(msg: String) 不反回任何值
5 case class Tell(msg: String) extends Interact[Unit]

由于Ask和Tell都不会返回泛类值,所以无需或者无法实现map函数,Interact[A]是个不是Functor的高阶类。我们必须把它转成Coyoneda提供给Free产生Monad:

1 Free.liftFC(Tell("hello"))                        //> res0: scalaz.Free.FreeC[Exercises.freelift.Interact,Unit] = Suspend(scalaz.Coyoneda$$anon$22@71423665)
2 Free.liftFC(Ask("how are you"))                   //> res1: scalaz.Free.FreeC[Exercises.freelift.Interact,String] = Suspend(scalaz.Coyoneda$$anon$22@20398b7c)

我们看看liftFC函数定义:

/** 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 lift s)

Coyoneda lift s 返回结果Coyoneda[S,A], liftFU用Unapply可以把Coyoneda[S,A]转化成S[A]并提供给liftF。看看Unapply这段:

 /**Unpack a value of type `M0[A0, B0]` into types `[a]M0[a, B0]` and `A`, given an instance of `TC` */
  implicit def unapplyMAB1[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[α, B0]})#λ]): Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[X, B0]
    type A = A0
  } = new Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[X, B0]
    type A = A0
    def TC = TC0
    def leibniz = refl
  }

  /**Unpack a value of type `M0[A0, B0]` into types `[b]M0[A0, b]` and `B`, given an instance of `TC` */
  implicit def unapplyMAB2[TC[_[_]], M0[_, _], A0, B0](implicit TC0: TC[({type λ[α] = M0[A0, α]})#λ]): Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[A0, X]
    type A = B0
  } = new Unapply[TC, M0[A0, B0]] {
    type M[X] = M0[A0, X]
    type A = B0
    def TC = TC0
    def leibniz = refl
  }

好了。但是又想了想,一个是Functor的Interact又是怎样的呢?那Ask必须返回一个值,而这个值应该是个Free,实际上是代表下一个运算:

 1 package Exercises
 2 import scalaz._
 3 import Scalaz._
 4 import scala.language.higherKinds
 5 object interact {
 6 trait Interact[+A]
 7 case class Ask[Next](prompt: String, n: String => Next) extends Interact[Next]
 8 case class Tell[Next](msg: String, n: Next) extends Interact[Next]
 9
10 object interFunctor extends Functor[Interact] {
11   def map[A,B](ia: Interact[A])(f: A => B): Interact[B] = ia match {
12       case Tell(m,n) => Tell(m, f(n))
13       case g: Ask[A] => Ask[B](g.prompt, g.n andThen f)
14   }
15 }

所以Ask返回Next类型值,应该是个Free,代表下一个运算。

 

时间: 2024-09-16 22:48:28

Scalaz(32)- Free :lift - Monad生产线的相关文章

泛函编程(30)-泛函IO:Free Monad-Monad生产线

 在上节我们介绍了Trampoline.它主要是为了解决堆栈溢出(StackOverflow)错误而设计的.Trampoline类型是一种数据结构,它的设计思路是以heap换stack:对应传统递归算法运行时在堆栈上寄存程序状态,用Trampoline进行递归算法时程序状态是保存在Trampoline的数据结构里的.数据结构是在heap上的,所以可以实现以heap换stack的效果.这种以数据结构代替函数调用来解决问题的方式又为泛函编程提供了更广阔的发展空间.     我们知道,任何涉及IO的运

Scalaz(18)- Monad: ReaderWriterState-可以是一种简单的编程语言

 说道FP,我们马上会联想到Monad.我们说过Monad的代表函数flatMap可以把两个运算F[A],F[B]连续起来,这样就可以从程序的意义上形成一种串型的流程(workflow).更直白的讲法是:任何类型只要实现了flatMap就可以用for-comprehension, for{...}yield.在这个for{...}里我们可以好像OOP一样编写程序.这个for就是一种运算模式,它规范了在for{...}里指令的行为.我们正从OOP风格走入FP编程模式,希望有个最基本的FP编程模式使

Scalaz(11)- Monad:你存在的意义

 前面提到了scalaz是个函数式编程(FP)工具库.它提供了许多新的数据类型.拓展的标准类型及完整的一套typeclass来支持scala语言的函数式编程模式.我们知道:对于任何类型,我们只需要实现这个类型的typeclass实例就可以在对这个类型施用所对应typeclass提供的所有组件函数了(combinator).突然之间我们的焦点好像都放在了如何获取typeclass实例上了,从而忽略了考虑为什么要使用这些typeclass及使用什么样的typeclass这些问题了.所以可能有人会问我

台湾面板产能不足大陆22亿后续大单现缺口

每经记者 岳伟 发自北京 由中国电子视像行业协会带领的大陆赴台湾面板采购团于前日回京.据了解,此次台湾行,9家整机生产商组成的大陆代表团与台湾面板生产商确定了22亿美元的采购大单,并将于今年全部落实. 在取得累累硕果的同时,此次台湾行的"领队"――中国电子视像行业协会秘书长白为民仍有忧虑.应厂商要求,这次交流将2009年面板采购金额从年初的22亿美元翻番至44亿美元,但不久前韩日企业大单的介入,加上面板上游企业至今仍未恢复元气,增加的22亿大单能否落实并没有十足的把握. 新增订单并不确

写出你所知道的java框架

问题描述 一直对框架的概念有点疑问.你用过的java写的框架有哪些?哪些是现在最常用的?非java的常用框架又有哪些呢? 解决方案 解决方案二:有疑问就google.解决方案三:百度也行,google老是访问有问题.解决方案四:这问题...意义何在解决方案五:意义在于我理解框架的概念,GWT是框架吗,我觉得是解决方案六:多如牛毛,上javaeye上看,很多人在写java框架解决方案七:http://en.wikipedia.org/wiki/Framework框架不是一个十分精确的概念,一个东西

Java社区目前的现状——交易

这是关于一笔交易的故事. 没有人为交易签过字. 但这仍然是一笔重要的交易. 这是Java的主人和Java社区之间的交易. 交易 这是我对Java的主人和Java社区之间如何相互影响作用的观点: Java的主人进行巨额投资. 社区使其意义重大. 所谓"Java的主人",我指的是Sun,之后是Oracle. 所谓"巨额投资",我指的是资金,开发耗时,市场推广和精力. 所谓"意义重大",我指的是被广泛关注和使用. 关键点是,这是个相互依存的关系.主人的

Scalaz(25)- Monad: Monad Transformer-叠加Monad效果

  中间插播了几篇scalaz数据类型,现在又要回到Monad专题.因为FP的特征就是Monad式编程(Monadic programming),所以必须充分理解认识Monad.熟练掌握Monad运用.曾经看到一段对Monad的描述:"Monadic for-comprehension就是一种嵌入式编程语言,由它的Monad提供它的语法".但如果每一种Monad的for-comprehension都独立提供一套语法的话,这种编程语言就显得十分单调.功能简单了.那么既然是FP,我们应该可

Scalaz(17)- Monad:泛函状态类型-State Monad

  我们经常提到函数式编程就是F[T].这个F可以被视为一种运算模式.我们是在F运算模式的壳子内对T进行计算.理论上来讲,函数式程序的运行状态也应该是在这个运算模式壳子内的,也是在F[]内更新的.那么我们就应该像函数式运算T值一样,也有一套函数式更新程序状态的方法.之前我们介绍了Writer Monad.Writer也是在F[]内维护Log的,可以说是一种状态维护方式.但Writer的Log是一种Monoid类型,只支持Semigroup的a|+|b操作,所以只能实现一种两段Log相加累积这种效

Scalaz(28)- ST Monad :FP方式适用变量

    函数式编程模式强调纯代码(pure code),主要实现方式是使用不可变数据结构,目的是函数组合(composability)最终实现函数组件的重复使用.但是,如果我们在一个函数p内部使用了可变量(mutable variables),如果函数的输入参数e是纯代码,那么表达式p(e)同样是纯代码的,因为函数的调用者是无法接触到函数内部申明的这些可变量的.不过,这样的做法会造成函数的臃肿代码,因为在函数内部是无法实现函数组合的,无法重复使用函数组件,实际上又违背了FP的宗旨.Scalaz提