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

摘要

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 {
      private[this] val name: String = _;
        def name: String = Test.this.name;
      private[this] val req: com.eoi.lib.http.Req = _;
        def req: com.eoi.lib.http.Req = Test.this.req;
    def (name: String, req: com.eoi.lib.http.Req): com.eoi.lib.http.Test = {
      Test.super.();
      ()
    };
     def copy(name: String = name, req: com.eoi.lib.http.Req = req): com.eoi.lib.http.Test = new Test(name, req);
     def copy$default$1: String = Test.this.name;
     def copy$default$2: com.eoi.lib.http.Req = Test.this.req;
    override  def productPrefix: String = "Test";
     def productArity: Int = 2;
     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  def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](Test.this);
     def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[com.eoi.lib.http.Test]();
    override  def hashCode(): Int = scala.runtime.ScalaRunTime._hashCode(Test.this);
    override  def toString(): String = scala.runtime.ScalaRunTime._toString(Test.this);
    override  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
}.&&({
       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 (): com.eoi.lib.http.Test.type = {
      Test.super.();
      ()
    };
    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  def apply(name: String, req: com.eoi.lib.http.Req): com.eoi.lib.http.Test = new Test(name, req);
    case  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));
     private def readResolve(): Object = com.eoi.lib.http.Test
  };
  case class Req extends AnyRef with Product with Serializable {
      private[this] val id: String = _;
        def id: String = Req.this.id;
    def (id: String): com.eoi.lib.http.Req = {
      Req.super.();
      ()
    };
     def copy(id: String = id): com.eoi.lib.http.Req = new Req(id);
     def copy$default$1: String = Req.this.id;
    override  def productPrefix: String = "Req";
     def productArity: Int = 1;
     def productElement(x$1: Int): Any = x$1 match {
      case 0 => Req.this.id
      case _ => throw new IndexOutOfBoundsException(x$1.toString())
    };
    override  def productIterator: Iterator[Any] = scala.runtime.ScalaRunTime.typedProductIterator[Any](Req.this);
     def canEqual(x$1: Any): Boolean = x$1.$isInstanceOf[com.eoi.lib.http.Req]();
    override  def hashCode(): Int = scala.runtime.ScalaRunTime._hashCode(Req.this);
    override  def toString(): String = scala.runtime.ScalaRunTime._toString(Req.this);
    override  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
}.&&({
       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))
    }))
  };
   object Req extends scala.runtime.AbstractFunction1[String,com.eoi.lib.http.Req] with Serializable {
    def (): com.eoi.lib.http.Req.type = {
      Req.super.();
      ()
    };
    final override  def toString(): String = "Req";
    case  def apply(id: String): com.eoi.lib.http.Req = new Req(id);
    case  def unapply(x$0: com.eoi.lib.http.Req): Option[String] = if (x$0.==(null))
      scala.None
    else
      Some.apply[String](x$0.id);
     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这个工具来一探糖背后的组成。

你可能感兴趣的:(记录一次函数式编程语法糖拆解过程)