petitviolet blog

    Akka-HTTP - DSLにおける暗黙の型変換

    2016-05-28

    QiitaScalaAkkaAkka-HTTP

    akka.http.scaladslパッケージを import することで使えるようになる DSL が、 裏側でどのように実装されているか気になったので調べた。

    題材

    akka-http において、単純なルーティングについて考えてみる。

    val route = path("ping") {
      get {
        complete("pong")
      }
    }
    

    localhost:8080/pingに GET リクエストを送ると期待通りにpongと返ってくる。

    $ curl localhost:8080/ping
    pong
    

    implicit conversion を明示する

    このrouteRouteたるために、implicit conversion がふんだんに使われている。 implicit な conversion を明示的にして変数に束縛して型注釈をつけるとこうなる。

    // path
    val pingSegment: String = "ping"
    val pingMatcher: PathMatcher[Unit] = ImplicitPathMatcherConstruction.segmentStringToPathMatcher(pingSegment)
    val pingDirective: Directive[Unit] = path(pingMatcher)
    
    // response
    val pongStr: String = "pong"
    val pongMarshal: ToResponseMarshallable =
      ToResponseMarshallable.apply(pongStr)(PredefinedToEntityMarshallers.StringMarshaller)
    val pongStandardRoute: StandardRoute = complete(pongMarshal)
    
    // construct route
    val getPingPath: StandardRoute => Route = s =>
      Directive.addByNameNullaryApply(MethodDirectives.get)(s)
    val pingRoute: Route = getPingPath(pongStandardRoute)
    val pingRouter: (=> Route) => Route = Directive.addByNameNullaryApply(pingDirective)
    val route: Route = pingRouter(pingRoute)
    

    思ったより量が多くなった。 なお implicit conversion を明記だけなら多少は読みやすくなる。

    Directive.addByNameNullaryApply(path(ImplicitPathMatcherConstruction.segmentStringToPathMatcher("ping"))) {
      Directive.addByNameNullaryApply(MethodDirectives.get) {
        complete(ToResponseMarshallable("pong"))
      }
    }
    

    DSL はどのように implicit conversion されているか

    実際に DSL を使用するだけであれば特に意識する必要はないが、DSL の裏で行われていることをざっとまとめると以下。

    • pathの引数に渡しているStringPathMatcher[Unit]に implicit conversion される
      • PathMatcher[L]になる場合(pathに正規表現を渡すとか)もあり、その場合はDirective[L]となる
        • Directive[L]L => Routeな関数を受け取る
    • completeの引数に渡しているStringToResponseMarshallableに implicit conversion される
      • 標準で用意されている Marshaller はPredefinedToEntityMarshallersを参照
      • その他の型については自前で Marshaller を実装すれば良い
    • Directive[L]Directive.addByNullaryApplyによってRoute => RouteFunction1に implicit conversion される
      • 今回はDirective0、すなわちDirective[Unit]なのでDirective.addByNameNullaryApplyが使われる
      • Directive[L]の場合はDirective.addDirectiveApply[L]が使われる

    from: https://qiita.com/petitviolet/items/5cf2b3d3defed0459b9e