Elmでサーバと通信する(Cmd)

　最近Elmに関する記事を連続して書いています。それはフロントエンドのプログラムもHaskellのようなすっきりとした文法で書ければいいなと思うからです。開発効率も上げたいですし。そして現状どこまで書くことが可能なのかを知りたいという動機からです。React無しでやれるのか？それとも既存のJavaScriptと併用するのが現実的なのか、どうやって？

　今までの感触だと、Model-View-Updateループは素晴らしくシンプルに抽象化されていて、学習コストが低く、その範囲内のものであれば、開発効率はすごく高くなる気がします。その範囲というのは、ユーザINPUTのイベント処理をupdateで書いて、その時生じたModelの変更を、viewで画面に反映するというものです。この範囲で済むプログラムならほとんどの仕事をElm Runtimeが自動的に行ってくれるので楽ちんです。乱数を扱うときは、Cmdを使いますが、これもまあわかってしまえば簡単です。それではHttp通信はどうでしょう？今回のテーマです。

　今回はHttpについてです。例によって公式サイトを題材にしていきたいと思います。
https://guide.elm-lang.org/architecture/effects/http.html


　またElm のCmdアーキテクチャの詳細については以下の記事を参照してください。今回はHttpに焦点を当てたいと思います。
https://qiita.com/sand/items/4efd8eeafd2c33778e08


```elm:
-- Read more about this program in the official Elm guide:
-- https://guide.elm-lang.org/architecture/effects/http.html

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Decode

main =
  Html.program
    { init = init "cats"
    , view = view
    , update = update
    , subscriptions = subscriptions
    }

-- MODEL
type alias Model =
  { topic : String
  , gifUrl : String
  }


init : String -> (Model, Cmd Msg)
init topic =
  ( Model topic "waiting.gif"
  , getRandomGif topic
  )


-- UPDATE
type Msg
  = MorePlease
  | NewGif (Result Http.Error String)


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    MorePlease ->
      (model, getRandomGif model.topic)

    NewGif (Ok newUrl) ->
      (Model model.topic newUrl, Cmd.none)

    NewGif (Err _) ->
      (model, Cmd.none)


-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ h2 [] [text model.topic]
    , button [ onClick MorePlease ] [ text "More Please!" ]
    , br [] []
    , img [src model.gifUrl] []
    ]


-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.none


-- HTTP
getRandomGif : String -> Cmd Msg
getRandomGif topic =
  let
    url =
      "https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=" ++ topic
  in
    Http.send NewGif (Http.get url decodeGifUrl)


decodeGifUrl : Decode.Decoder String
decodeGifUrl =
  Decode.at ["data", "image_url"] Decode.string
```

　このプログラムは、猫の画像を表示するものです。ボタンを押してRequestを発行するたびに、ランダムな猫画像のURLが返され(Result)てきます。

　以下に詳細を見ていきます。まずmainですが、これは乱数の時と同じです。
https://qiita.com/sand/items/4efd8eeafd2c33778e08

```elm:main
main = Html.program { init = init "cats", view = view, update = update, subscriptions = subscriptions}
```

　次にinitですが、画像urlが"waiting.gif"という存在しないurlなので一瞬表示が崩れます。しかしコマンドgetRandomGif topicが発行され正しいurlが返ってくるので表示は最初の猫画像を示します。

```elm:init
init : String -> (Model, Cmd Msg)
init topic =
  ( Model topic "waiting.gif"
  , getRandomGif topic
  )
```

　次にMsgの型ですが、MorePleaseがボタンを押したときのupdateに使われ、NewGifがサーバからurlをgetするというコマンドの処理が終わったときのupdateに使われます。

```elm:Msg
type Msg
  = MorePlease
  | NewGif (Result Http.Error String)
```

　Resultは一般的に以下のように定義されたElmの型です。失敗か成功かの値を表現できます。この場合、errorとしてはHttp.Error型が、ValueとしてはString型が期待されています。
http://package.elm-lang.org/packages/elm-lang/core/latest/Result

```elm:Result
type Result error value
    = Ok value
    | Err error
```

　updateは形としては乱数の時と同じです。ボタンクリック時のコマンド(getRandomGif model.topic)が違うのと、コマンドの返却値としてerrorのパタンが追加されたのが異なります。

```elm:update
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    MorePlease ->
      (model, getRandomGif model.topic)

    NewGif (Ok newUrl) ->
      (Model model.topic newUrl, Cmd.none)

    NewGif (Err _) ->
      (model, Cmd.none)
```

　さてgetRandomGifこそが、サーバとHttp通信するためのコマンドを生み出す関数です。

```elm:getRandomGif
getRandomGif : String -> Cmd Msg
getRandomGif topic =
  let
    url =
      "https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=" ++ topic
  in
    Http.send NewGif (Http.get url decodeGifUrl)
```

　特に以下の行に注目します。

```
    Http.send NewGif (Http.get url decodeGifUrl)
```

　この一行は一見複雑に関数を組み合わせていますが、要するにCmdを創り出す関数を呼んでいます。以下の型宣言が理解を厳密なものとしてくれます。
http://package.elm-lang.org/packages/elm-lang/http/latest/Http


 Msg型のコンストラクタNewGifは以下のような関数とみることができます。

```elm:NewGif関数の型宣言
NewGif : Result Error value -> msg
```

　Http.getは定義により以下の型になります。

```elm:Http.get関数の型宣言
Http.get : String -> Decode.Decoder value -> Http.Request value
```

```elm:decodeGifUrlの型宣言
decodeGifUrl : Decode.Decoder String
```


　上の3つの型がちょうど以下のHttp.sendの型に当てはまります。値としてCmd msgを得ることができます。

```elm:Http.send関数の型宣言
Http.send : (Result Error value -> msg) -> Http.Request value -> Cmd msg
```


　さてここでわからないのがサーバ側がReturnしてくれる実際の値です。modelのgifUrlは都度変更されますが、model.topicはcatsのままで変更されないことに注意しましょう。以下のリクエストをブラウザから直接発行してみます。

```:Request
https://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=cats
```

　以下がその時にReturnされてくるjsonです。

```json:Result
{
    "data": {
        "caption": "", 
        "fixed_height_downsampled_height": "200", 
        "fixed_height_downsampled_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/200_d.gif", 
        "fixed_height_downsampled_width": "299", 
        "fixed_height_small_height": "100", 
        "fixed_height_small_still_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/100_s.gif", 
        "fixed_height_small_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/100.gif", 
        "fixed_height_small_width": "150", 
        "fixed_width_downsampled_height": "134", 
        "fixed_width_downsampled_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/200w_d.gif", 
        "fixed_width_downsampled_width": "200", 
        "fixed_width_small_height": "67", 
        "fixed_width_small_still_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/100w_s.gif", 
        "fixed_width_small_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/100w.gif", 
        "fixed_width_small_width": "100", 
        "id": "l41lTEXcm2fWlcidy", 
        "image_frames": "40", 
        "image_height": "214", 
        "image_mp4_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/giphy.mp4", 
        "image_original_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/giphy.gif", 
        "image_url": "https://media1.giphy.com/media/l41lTEXcm2fWlcidy/giphy.gif", 
        "image_width": "320", 
        "type": "gif", 
        "url": "http://giphy.com/gifs/afvpets-cats-pets-afv-l41lTEXcm2fWlcidy", 
        "username": "afvpets"
    }, 
    "meta": {
        "msg": "OK", 
        "response_id": "5a345ffb47626a30733460a4", 
        "status": 200
    }
}
```

　さて最後になりますが、このJson値はelmオブジェクトに変換され、decodeGifUrl で望む項目の値にアクセスします。つまりdata階層のimage_urlの値をgetします。
https://guide.elm-lang.org/interop/json.html


```elm:decodeGifUrl 
decodeGifUrl : Decode.Decoder String
decodeGifUrl =
  Decode.at ["data", "image_url"] Decode.string
```


　今回のHttp通信は前回の乱数と同じようにCmdを通してElm Runtimeに仕事を依頼するものでした。ElmのHttp moduleにはhttp通信のためのAPIが一式そろっています。このCmdアーキテクチャの枠組みで、Emlでも安心してサーバとの通信が行えそうです。