記錄一次函數(shù)式編程語法糖拆解過程

摘要

Scala是一種集合了面向?qū)ο笠约懊嫦蚝瘮?shù)式的基于jvm的編程語言,所以編寫scala代碼,既可以完全類似java一樣的風格,也可以寫出基于函數(shù)式的天馬行空的‘優(yōu)雅’的代碼,spark里面rdd相關的代碼就是scala函數(shù)式編程的極致體現(xiàn),真正做到了到最后一刻才會計算的延遲加載,不過相比面向?qū)ο蟮娘L格,函數(shù)式編程風格的代碼有時會難以理解,實際的執(zhí)行順序難以琢磨,下面是一個我最近看過的一個例子,簡單記錄一下。

解讀過程

代碼如下,入口在方法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
  }
}

比較明顯可以看出的是,這個方法的輸入?yún)?shù)是一個函數(shù)(輸入為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)

執(zhí)行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方法去糖之后的樣子,可以看到這個方法的輸入?yún)?shù)是一個函數(shù),并且返回值也是一個函數(shù),并且作為返回值的這個函數(shù)的輸入?yún)?shù)為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)));

看到這里就完全明白了。最開始例子里面的反回值也是一個函數(shù):RequestContext => Future[RouteResult],只是目前的這種寫法不是太明顯,如果能像去糖后把返回值的結(jié)構(gòu)清晰的定義出來,就一目了然了。如果我們看一下akka http庫里面關于路由Route的定義,就會發(fā)現(xiàn)兩者是一致的,所以在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")
}

結(jié)論

函數(shù)式編程風格的代碼有時確實比較難讀懂,特別是在scala當中,結(jié)合implicit這種特性,就靈活度更大。如果一時看不懂,可以試著換種角度去理解(本文中我們在探尋ctx是哪來的,沒想到它卻是返回值的一部分),當然更推薦使用scalac這個工具來一探糖背后的組成。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容