関数型よく知らない人のelm入門(2) - Http処理をバラす
Elm入門第二弾、今回はHttp通信のお話です。
公式の猫画像通信サンプルですが、詳細を見ていくとわけわからないという人も少なくないのではないでしょうか?
特に次の1行
Http.send NewGif (Http.get url decodeGifUrl)
今回はこの1行にフォーカスしていきます。
基本構造
まず前提として、 Http.getはリクエストを生成するだけ です。通信はしません。
Javaでいうところの、 HttpRequest.create(Method.GET, String url)
のようなものです(※こんなメソッドはないですがイメージとして)
そして、 Http.send が実際に送信する部分になります。 Javaでいうところの conn.connect()
のようなものです。
(※実際に通信するという言い方、正確にはおそらく異なる。「通信するという命令」というほうが的確かもしれません)
では真ん中にある NewGif って何? decodeGifUrlって何?
これらを公式リファレンスを紐解きながら見ていきましょう。
Http.get
まずは get関数
を見ていきます。
公式リファレンス によると、get関数は
get : String -> Decoder a -> Request a
とあります。第一引数がURLなのは分かりやすいですね。では Decoder とは何なのか?
結論からいいますと、 Javaで例えるならJsonParser です。現にDecoderは、Jsonモジュール に存在します。
つまり第二引数は、「通信結果として受け取った文字列を、特定の型に変換するためのJsonParser」になります。(※elmは通信結果を、生の文字列取得かJsonとしてしか受け取れないようです)
よってこのget関数自体をJavaっぽくしてみると
//Decoderのイメージ interface JsonParser<T> { T decode(String jsonText); } //get関数自体 static <T> Request<T> get(String url, JsonParser<T> parser);
となります。最もDecoderは結構面倒なクラスですが…これはまた別の機会に。
NewGif
さて、一番右にある Http.get が片付いたので、次は NewGif です。
公式のサンプル を再びみると、NewGifを見つけることができます。
type Msg = MorePlease | NewGif (Result Http.Error String)
つまり、NewGifは「型」といえます(値ともいえるでしょうが…)。
…え?型を引数に渡している?
前回の記事で書きましたとおり、elmでのジェネリクスは引数のように指定します(実際に引数なのでしょうが…)
list : List Int //List<Integer>相当 list = [1, 2, 3]
じゃあこれもジェネリクス?
半分正解で半分間違い、というところでしょうか?
あくまでこれはsend関数の第一引数なので、send関数自体も見てみましょう。
Http.send
先ほども書きましたように、実際に通信を開始するのがこの関数です。公式リファレンスによると、sendは
send : (Result Error a -> msg) -> Request a -> Cmd msg
関数型になれてない人間が見ると本当にわけわからんですね….(自分含む)
第二引数は分かりやすいとおもいます。Http.get/Http.postで生成したRequestクラスです (理解のため意図的にクラスと書いてます)
戻り値も前回の記事を参照すれば理解できる…かも。Cmd=Command、ようは「何らかの副作用を伴う命令」と思ってください。
では第一引数は?
Result Error a -> msg…?本当に初見殺しですね…
まずResult。Resultはエラーが発生する可能性があるときに使うクラスで、Java風に書くならこんな感じです。
//一部省略します。 class Result<ErrorT, ResultT> { ErrorT error; ResultT result; private Result(Error e, Result r) { error = e; result = r; } public static Result<~> createError(ErrorT error) { return Result(error, null); } public static Result<~> createResult(ResultT result) { return Result(null, result); } }
errorかresultのどちらかの値をとる、というのが上記で伝わるといいのですが。(完全にJava互換は無理ですね…)
さらに Result Error a
の Error は標準のエラーで使われるクラスです。なのでこれをもっとJava風にかくなら、 Result<Throwable, T>
というところでしょうか?
第一引数、 Result Error a -> msg
に戻ってみましょう。
a というのはようはジェネリクスでしたね。つまり、成功時の戻り値の型はこちらで指定する、ということです。
さらにmsgもジェネリクスといえます。msgというのは前回の記事で書いたように、「update関数に渡される自分で定義した型」になります。
よってこの send関数 の第一引数は、通信結果をMsg型に変換する関数 になります。
恒例、send関数をJavaっぽくしてみましょう。
//CmdはJava標準だとCallableが近いような気がしてきたので static <ResultT, MsgT> Callable<MsgT> send( //ResultからMsgを返すLambda Function<Result<Throwable, ResultT>, MsgT> converter, //ResultTを返すHttpRequest Request<ResultT> request) { //実装のイメージ。あくまでイメージです Callable<MsgT> callable = () -> { //通信し、結果をresultに格納 Result<Throwable, ResultT> result try { //通信し、Request生成時に指定したパーサーでJSONパース String response = request.connect().readString()); ResultT r = request.parseJson(response); result = Result.success(r); } catch (Thowable e) { result = Result.error(e); } //第一引数でMsg型に変換し返す MsgT msg = converter.apply(result); return msg; } //このcallableをExecutorに入れるのは elm-Framework のお仕事(と勝手に理解してます) return callable; }
こんな感じでしょうか?ご覧の通り大量のジェネリクスですね。このようにして、通信結果をMsgに変換してます。
これでsend関数に何を渡すか理解できましたね。では先ほどの NewGif
に戻ってみましょう。
先ほどのsend関数の部分で見た通り、sendの第一引数は「ResultをMsgに変換する関数」でしたね。
そして公式サンプルでは、 Http.send NewGif request
と書かれていました。
ということは NewGifは関数…? そのとおり、関数です!
再びNewGifの定義を見てみましょう。
type Msg = MorePlease | NewGif (Result Http.Error String)
慣れないと分かりづらいかもですが、NewGifインスタンスは value = NewGif (Result Ok "Test")
のように記述し生成します。
上の式を良く見ると、NewGifというのは「NewGif型を生成する関数」である、ということが分かりますでしょうか?
Javaでは NewGif::new
相当といえるでしょう。よってNewGifとsendの呼び出し部分
NewGif (Result Http.Error String) send NewGif 第二引数
をJavaにすると
class NewGif { NewGif(Result<~> result) {略} } //メソッド参照 send(NewGif::init, 第二引数) //上記に慣れてない方向け。同じです send( (Result<~> result) -> { return new NewGif(result) }, 第二引数)
となります。NewGifが型であると同時に関数でもあるので厄介ですね…(厳密にはMsgが型で、NewGifは値となるのかもしれませんが)
どちらにせよ、これで NewGifはNewGifのコンストラクタ であることが分かりました。
まとめる
最後にこれまでの内容を全部まとめてJava化しましょう!
まず元となった elm の send および NewGif です。
type Msg = MorePlease | NewGif (Result Http.Error String) getRandomGif : String -> Cmd Msg getRandomGif topic = let url = 略 in Http.send NewGif (Http.get url decodeGifUrl)
ではいきます、一気にかきます!
//Msgを強引にJava化するとこんな感じか? interface Msg { } class MorePlease implements Msg { } class NewGif implements Msg { public final Result<Throwable, String> result; NewGif(Result<Throwable, String> result) { this.result= result; } } //通信処理を記述する部分 static Callable<Msg> getRandomGif(String topic) { //request生成 String url = 略 Parser<String> parser = /*JSONからURLを抽出する処理*/; Http.Request request = Http.get(url, parser) //sendにわたす return Http.send(NewGif::new, request); }
通信から結果表示までの流れをしますと、
- ボタンクリックでMsg発生、 update が呼び出される
- update内で getRandomGif が呼び出され、
callable
を返す - 返ったcallableが elm-framework内でExecutorに渡されて実行される
- callableの処理が走り通信する
- 通信結果から
Parser
を用いJSONパース、Stringがとりだされる - StringからNewGifインスタンス生成 、elm-frameworkに渡される
- elmが戻り値のNewGifを 再度update関数に渡す*
- update関数でModel更新、view関数が呼び出される
- view関数で該当URLを指定した imgタグ を生成、レンダリング
という流れになります。
elmの実際の挙動を正確に解説してるわけではないと思いますが、なんとなくのイメージをつかめると幸いです。
にしてもelmのシンタックスハイライトはどの言語を指定するのがいいんでしょうね?さすがにelmはないですから難しい。