美文网首页
记录一次函数式编程语法糖拆解过程

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

作者: JackyYin | 来源:发表于2017-09-15 16:15 被阅读0次

    摘要

    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这个工具来一探糖背后的组成。

    相关文章

      网友评论

          本文标题:记录一次函数式编程语法糖拆解过程

          本文链接:https://www.haomeiwen.com/subject/agzssxtx.html