记录一次函数式编程语法糖拆解过程

摘要

Scala是一种集合了面向对象以及面向函数式的基于jvm的编程语言,所以编写scala代码,既可以完全类似java一样的风格,也可以写出基于函数式的天马行空的‘优雅’的代码,spark里面rdd相关的代码就是scala函数式编程的极致体现,真正做到了到最后一刻才会计算的延迟加载,不过相比面向对象的风格,函数式编程风格的代码有时会难以理解,实际的执行顺序难以琢磨,下面是一个我最近看过的一个例子,简单记录一下。

解读过程

代码如下,入口在方法def imperativelyComplete,这个方法的主要目的是基于akka http的RequestContext进行封装,把当前http请求上下文传给其他地方进行处理,不过这个不是本文的重点,本文的重点是imperativelyComplete方法的定义,大家可以先自行观看几秒钟。

final class ImperativeRequestContext(ctx: RequestContext, promise: Promise[RouteResult]) {
  private implicit val ec = ctx.executionContext

  val request = ctx.request

  def complete(obj: ToResponseMarshallable): Unit = ctx.complete(obj).onComplete(promise.complete)

  def fail(error: Throwable): Unit = ctx.fail(error).onComplete(promise.complete)
}

object ImperativeRequestContext {
  def imperativelyComplete(inner: ImperativeRequestContext => Unit): Route = {
    ctx: RequestContext =>
      val p = Promise[RouteResult]()
      inner(new ImperativeRequestContext(ctx, p))
      p.future
  }
}

比较明显可以看出的是,这个方法的输入参数是一个函数(输入为ImperativeRequestContext类型,没有返回值),这种定义方法还是比较多见的,比如Loan Pattern

def withPrintWriter(file:File)(op : PrintWriter => Unit) = {
  val writer = new PrintWriter(file)
  try{
    op(writer)
  } finally{
    writer.close()
  }
}

接着再看这个方法的方法体,会有一个类似类定义里面的self annotation的 ctx:RequestContext,看到这里有些同学可能就比较头晕了,这个ctx是哪来的?? 别着急,都知道scala糖多,我们可以借助scalac这个工具,来看看去糖之后的样子,为了去除掉不必要的依赖,让scalac可以编译通过,我写了一个类似的例子(Test.scala):

case class Test(name:String, req:Req)

object Test {
  def imperativelyComplete(inner: Test => Unit) = {
    ctx:Req =>
      inner(Test("test", ctx))
  }
}

case class Req(id:String)

执行scalar -Xprint:typer Test.scala

[[syntax trees at end of                     typer]] // Test.scala
package com.eoi.lib.http {
  case class Test extends AnyRef with Product with Serializable {
    <caseaccessor> <paramaccessor> private[this] val name: String = _;
    <stable> <caseaccessor> <accessor> <paramaccessor> def name: String = Test.this.name;
    <caseaccessor> <paramaccessor> private[this] val req: com.eoi.lib.http.Req = _;
    <stable> <caseaccessor> <accessor> <paramaccessor> def req: com.eoi.lib.http.Req = Test.this.req;
    def <init>(name: String, req: com.eoi.lib.http.Req): com.eoi.lib.http.Test = {
      Test.super.<init>();
      ()
    };
    <synthetic> def copy(name: String = name, req: com.eoi.lib.http.Req = req): com.eoi.lib.http.Test = new Test(name, req);
    <synthetic> def copy$default$1: String = Test.this.name;
    <synthetic> def copy$default$2: com.eoi.lib.http.Req = Test.this.req;
    override <synthetic> def productPrefix: String = "Test";
    <synthetic> def productArity: Int = 2;
    <synthetic> def productElement(x$1: Int): Any = x$1 match {
      case 0 => Test.this.name
      case 1 => Test.this.req
      case _ => throw new IndexOutOfBoundsException(x$1.toString())
    };
    override <synthetic> def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](Test.this);
    <synthetic> def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[com.eoi.lib.http.Test]();
    override <synthetic> def hashCode(): Int = scala.runtime.ScalaRunTime._hashCode(Test.this);
    override <synthetic> def toString(): String = scala.runtime.ScalaRunTime._toString(Test.this);
    override <synthetic> def equals(x$1: Any): Boolean = Test.this.eq(x$1.asInstanceOf[Object]).||(x$1 match {
  case (_: com.eoi.lib.http.Test) => true
  case _ => false
}.&&({
      <synthetic> val Test$1: com.eoi.lib.http.Test = x$1.asInstanceOf[com.eoi.lib.http.Test];
      Test.this.name.==(Test$1.name).&&(Test.this.req.==(Test$1.req)).&&(Test$1.canEqual(Test.this))
    }))
  };
  object Test extends scala.AnyRef with Serializable {
    def <init>(): com.eoi.lib.http.Test.type = {
      Test.super.<init>();
      ()
    };
    def imperativelyComplete(inner: com.eoi.lib.http.Test => Unit): com.eoi.lib.http.Req => Unit = ((ctx: com.eoi.lib.http.Req) => inner.apply(Test.apply("test", ctx)));
    case <synthetic> def apply(name: String, req: com.eoi.lib.http.Req): com.eoi.lib.http.Test = new Test(name, req);
    case <synthetic> def unapply(x$0: com.eoi.lib.http.Test): Option[(String, com.eoi.lib.http.Req)] = if (x$0.==(null))
      scala.None
    else
      Some.apply[(String, com.eoi.lib.http.Req)](scala.Tuple2.apply[String, com.eoi.lib.http.Req](x$0.name, x$0.req));
    <synthetic> private def readResolve(): Object = com.eoi.lib.http.Test
  };
  case class Req extends AnyRef with Product with Serializable {
    <caseaccessor> <paramaccessor> private[this] val id: String = _;
    <stable> <caseaccessor> <accessor> <paramaccessor> def id: String = Req.this.id;
    def <init>(id: String): com.eoi.lib.http.Req = {
      Req.super.<init>();
      ()
    };
    <synthetic> def copy(id: String = id): com.eoi.lib.http.Req = new Req(id);
    <synthetic> def copy$default$1: String = Req.this.id;
    override <synthetic> def productPrefix: String = "Req";
    <synthetic> def productArity: Int = 1;
    <synthetic> def productElement(x$1: Int): Any = x$1 match {
      case 0 => Req.this.id
      case _ => throw new IndexOutOfBoundsException(x$1.toString())
    };
    override <synthetic> def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](Req.this);
    <synthetic> def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[com.eoi.lib.http.Req]();
    override <synthetic> def hashCode(): Int = scala.runtime.ScalaRunTime._hashCode(Req.this);
    override <synthetic> def toString(): String = scala.runtime.ScalaRunTime._toString(Req.this);
    override <synthetic> def equals(x$1: Any): Boolean = Req.this.eq(x$1.asInstanceOf[Object]).||(x$1 match {
  case (_: com.eoi.lib.http.Req) => true
  case _ => false
}.&&({
      <synthetic> val Req$1: com.eoi.lib.http.Req = x$1.asInstanceOf[com.eoi.lib.http.Req];
      Req.this.id.==(Req$1.id).&&(Req$1.canEqual(Req.this))
    }))
  };
  <synthetic> object Req extends scala.runtime.AbstractFunction1[String,com.eoi.lib.http.Req] with Serializable {
    def <init>(): com.eoi.lib.http.Req.type = {
      Req.super.<init>();
      ()
    };
    final override <synthetic> def toString(): String = "Req";
    case <synthetic> def apply(id: String): com.eoi.lib.http.Req = new Req(id);
    case <synthetic> def unapply(x$0: com.eoi.lib.http.Req): Option[String] = if (x$0.==(null))
      scala.None
    else
      Some.apply[String](x$0.id);
    <synthetic> private def readResolve(): Object = com.eoi.lib.http.Req
  }
}

下面这个就是imperativelyComplete方法去糖之后的样子,可以看到这个方法的输入参数是一个函数,并且返回值也是一个函数,并且作为返回值的这个函数的输入参数为Req类型

    def imperativelyComplete(inner: com.eoi.lib.http.Test => Unit): com.eoi.lib.http.Req => Unit = ((ctx: com.eoi.lib.http.Req) => inner.apply(Test.apply("test", ctx)));

看到这里就完全明白了。最开始例子里面的反回值也是一个函数:RequestContext => Future[RouteResult],只是目前的这种写法不是太明显,如果能像去糖后把返回值的结构清晰的定义出来,就一目了然了。如果我们看一下akka http库里面关于路由Route的定义,就会发现两者是一致的,所以在akka http的路由里面用imperativelyComplete这个方法,才可以编译通过。

package object server {

  type Route = RequestContext ⇒ Future[RouteResult]

  type RouteGenerator[T] = T ⇒ Route
  type Directive0 = Directive[Unit]
  type Directive1[T] = Directive[Tuple1[T]]
  type PathMatcher0 = PathMatcher[Unit]
  type PathMatcher1[T] = PathMatcher[Tuple1[T]]

  def FIXME = throw new RuntimeException("Not yet implemented")
}

结论

函数式编程风格的代码有时确实比较难读懂,特别是在scala当中,结合implicit这种特性,就灵活度更大。如果一时看不懂,可以试着换种角度去理解(本文中我们在探寻ctx是哪来的,没想到它却是返回值的一部分),当然更推荐使用scalac这个工具来一探糖背后的组成。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容