/* 注意!! この記事は、Scalaのプログラミングについて書かれています */
麗しき、@ponkotuy氏の作ったhttp-client-wrapperを改造して、Stateモナドを利用してみました。
使い勝手は、むしろ悪化した模様。(^^;
元プログラムの解説記事はこちらです。
モチベーション
元記事にもありますが、グローバルな変数をimplicitで引き渡すような設計となっていたので、これをなんとかしたいと思いました。
あと、request -> (response, session); request -> (response, session)という構造が見えたので、Stateパターンだなという直感が働き、実装してみたかったというのが一番の動機です。
実装してみる
Stateモナドとは、a -> (s, b)というパターンをモナドを使って数珠つなぎにしていく、設計パターンです。
Scalaの標準ライブラリには装備されていないので、scalazを利用しました。
scalazを使うのは、実は今回初めてで、ついにscalazデビューかと感慨深いです。(^^;;
実装に際しては、横田さんのこちらのページを参考にしました(厳密にはscalazでは無いですけど)。
前回のリクエストのセッションを受け取り、次のリクエストを発行して、更新されたセッションを返すという処理をGET, POST, PUTなどのメソッド分用意する感じになりました。
核となる部分は、下記の部分で、Stateクラスのインスタンスを生成しています。
private def buildRequest(method: Method, url: String)(f: Request => Request): State[Session, Response] = State[Session, Response] { case SomeSession(cookie, lastRes, lastReq, lastUrl) => val next = nextUrl(url, lastUrl) val req = f(Request(next.toString)).header("Cookie", cookie.toString) val newSession = invokeRequest(method, req, next) (newSession, newSession.lastRes) case EmptySession => val init = initUrl(url) val req = f(Request(init)) val newSession = invokeRequest(method, req, new URL(init)) (newSession, newSession.lastRes) }
Stateクラスは、コンストラクタに関数を受け取り、これが、Session => (Session, Response)という型を持つことになります。
地味に、初回はセッションがないので、case式にて場合分けして、2パターン書いています。
ちょっとゴミゴミしているところが残念なポイントです。
ソースコードは、下記にあります。
GitHub - qtamaki/http-client-state: The Skinny's HTTP Client Wrapper on CLI.
元のコードを9割型書き換えてしまった上に、使い勝手の向上もなく、まったく別物になってしまったため、プロジェクト名を変更しました。(^^;
結果
一応、当初の目的であった、グローバルなスコープを持つ変数や引き回しのためのimplicitを排除することはできました。
処理側は、インスタンス変数を持たず(というかobject)、データ型としてのcaseクラスが値を持つだけになっています。
モナドなので、forを使って処理を合成することもできます(そうしないとsessionを引き回せない)。
「ちょっとした検証にREPLでHttpClientを使う」という当初の用途からは後退した気がしますが、取り急ぎこんな感じでやってみました。(^^;