摘要
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這個工具來一探糖背后的組成。