ElmのHttpとJson.Decode、Taskの実践的な使い方

　Elmの基礎的な勉強を2週間程度行ってきました。なんとなく感じがわかったところでもう少し実践的なコードを書いてみたいと思いました。Httpの使い方をもう少し掘り下げたいと思います。今回はRest APIを叩く簡単なクライアントを作成します。ちなみにここ数年人気の脳科学者（中野信子さん）によると、新しいことを学習してから3週間ぐらいで回路ができてくるみたいです。もう少しでElmの脳回路ができる予定です。頑張ります。



# １．サーバ

　まずjson-serverをインストールします。

```sh
npm install -g json-server  
```

　json-serverはモック環境を提供してくれます。以下のようにdb.jsonファイルを作成します。

```json:db.json
{
 "path1": { "tags": ["猫","犬","クジラ","ヤギ","タカ"] }
,"path2": { "articles": 
    [
      { "title": "12月29日 晴れ", "story": "今日は晴れでした。"}
     ,{ "title": "12月30日 曇り", "story": "今日は大掃除をしました。エアコンの掃除は大変でした。"}
     ,{ "title": "12月31日 晴れのち雨", "story": "今日は大晦日です。夜更かしします。"}
    ] 
  }
}
 
```

　db.jsonがあるディレクトリで以下のようにしてサーバを立ち上げれば終了です。

```sh
json-server db.json --port 3090 
```

ブラウザから以下のURLを叩くと、それぞれ下記のJSONデータが表示されます。
http://www.mypress.jp:3090/path1
http://www.mypress.jp:3090/path2

```json:path1の表示結果
{
  "tags": [
    "猫",
    "犬",
    "クジラ",
    "ヤギ",
    "タカ"
  ]
}
```

```json:path2の表示結果
{
  "articles": [
    {
      "title": "12月29日 晴れ",
      "story": "今日は晴れでした。"
    },
    {
      "title": "12月30日 曇り",
      "story": "今日は大掃除をしました。エアコンの掃除は大変でした。"
    },
    {
      "title": "12月31日 晴れのち雨",
      "story": "今日は大晦日です。夜更かしします。"
    }
  ]
}
```

　以上を確認して、準備完了です。



# ２．Elmクライアントの動作

　Elmクライアントは、「Get Tags」と「Get Articles」、「Get Tags and Articles」という3個のボタンを表示します。「Get Tags」をクリックするとタグ一覧のみを表示します。「Get Articles」は記事一覧のみを表示します。「Get Tags and Articles」はタグ一覧と記事一覧を表示します。

まずは初期画面です。タグ一覧も記事一覧も空です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/a394cdb7-57a9-6287-9fe2-b39793326e9b.png)


「Get Tags」をクリックするとタグ一覧のみ表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/f91d736c-d0f7-a03b-095c-1ad024319084.png)


「Get Articles」をクリックすると記事一覧のみ表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/f0f84bd3-a26c-65df-bd38-6ef3c3469c96.png)


「Get Tags and Articles」をクリックするとタグ一覧と記事一覧が表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/61915/6a376c9e-af10-13f8-0baa-ed4a2d271817.png)




# ２．Elmクライアントのコード

　以下がソースコードになります。ポイントはHttpの使い方です。Json.Decodeと一緒に使います。また1画面を描画するのに、2回RestAPIを叩く必要がある場合に、2回Cmdを発行するのではなく1回のCmdの発行で済ます方法を示しています。２つのRequestをTaskに変換してから、2個のTaskをmap2で1個のTaskに束ねればOKです。以下に詳細を説明します。


```elm:Main.elm
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Http
import Json.Decode as Decode
import Task exposing (Task)

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


-- MODEL
type Tag
    = Tag String

type alias Article =
    { story : String
    , title : String
    }


type alias Model =
    { tags : List Tag
    , articles : List Article
    }


init : (Model, Cmd Msg)
init =
  ( Model [] []
  , Cmd.none
  )


-- UPDATE
type Msg
  = GetTags
  | GetArticles
  | GetTagsArticles
  | NewTags (Result Http.Error (List Tag))
  | NewArticles (Result Http.Error (List Article))
  | NewTagsArticles (Result Http.Error Model)


update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    GetTags ->
      (model, getTags)

    GetArticles ->
      (model, getArticles)

    GetTagsArticles ->
      (model, getTagsArticles)

    NewTags (Ok newtags) ->
      ( Model newtags [] , Cmd.none)

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

    NewArticles (Ok newarticles) ->
      ( Model [] newarticles , Cmd.none)

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

    NewTagsArticles (Ok newmodel) ->
      ( newmodel , Cmd.none)

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



-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ h2 [] [ text "ボタン一覧" ]
    , button [ onClick GetTags ] [ text "Get Tags" ]
    , br [] []
    , button [ onClick GetArticles ] [ text "Get Articles" ]
    , br [] []
    , button [ onClick GetTagsArticles ] [ text "Get Tags and Articles" ]
    , br [] []
    , h2 [] [ text "タグ一覧" ]
    , ul [] (List.map viewTags model.tags)
    , br [] []
    , h2 [] [ text "記事一覧" ]
    , ul [] (List.map viewArticles model.articles)
    ]

viewTags (Tag t) =
    li [] [ text t ]

viewArticles a =
    li []
      [ h3 [] [ text a.title]
      , p  [] [ text a.story]
      ]



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



-- HTTP
url_tags =
    "http://www.mypress.jp:3090/path1"

url_articles =
    "http://www.mypress.jp:3090/path2"


requestTags : Http.Request (List Tag)
requestTags =
    Http.get url_tags ( Decode.field "tags" ( Decode.list ( Decode.map Tag Decode.string ) ) )


requestArticles : Http.Request (List Article)
requestArticles =
    Http.get url_articles ( Decode.field "articles" ( Decode.list article ) )


article : Decode.Decoder Article
article =
    Decode.map2 toArticle (Decode.field "title" Decode.string) (Decode.field "story" Decode.string)

toArticle : String -> String -> Article
toArticle t s =
    { title=t, story=s }


getTags : Cmd Msg
getTags =
    Http.send NewTags requestTags

getArticles : Cmd Msg
getArticles =
    Http.send NewArticles requestArticles


getTagsArticles : Cmd Msg
getTagsArticles =
    Task.attempt NewTagsArticles ( Task.map2 toModel ( Http.toTask (requestTags) )  ( Http.toTask (requestArticles) ) )


toModel : List Tag -> List Article -> Model
toModel t a =
    { tags=t, articles=a }
```

　まずはJson.Decodeの使い方に集中しましょう。Json.DecodeはHttpで取得したJson文字列を、Elmの値に変換するための関数群です。
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode

　以下はタグ一覧を取得するためのRequestを作成しています。

```elm:requestTags
requestTags : Http.Request (List Tag)
requestTags =
    Http.get url_tags ( Decode.field "tags" ( Decode.list ( Decode.map Tag Decode.string ) ) )
```

　Http.getの型を確認しましょう。2番目の引数は Decoder a となっています。
http://package.elm-lang.org/packages/elm-lang/http/latest/Http

```elm:Http.get
get : String -> Decoder a -> Request a
```

　上で Decorder a は Decode.field "tags" ( Decode.list ( Decode.map Tag Decode.string ) )  ですね。これは前に示したように以下のJson文字列をDecodeするためのものです。


```json:
{
  "tags": [
    "猫",
    "犬",
    "クジラ",
    "ヤギ",
    "タカ"
  ]
}
```

　Decode.field "tags" でJson配列 ["猫","犬","クジラ","ヤギ","タカ"] を Decoder であるDecode.list ( Decode.map Tag Decode.string ) で Decodeします。これはElmのリストである[Tag "猫", Tag "犬",Tag "クジラ",Tag "ヤギ",Tag "タカ"] になります。

　以下が Decode.map の型になります。

```elm:Decoce.mapの型
map : (a -> value) -> Decoder a -> Decoder value
```


　次に記事一覧を取得するためのRequestを作成します。

```elm:requestArticles
requestArticles : Http.Request (List Article)
requestArticles =
    Http.get url_articles ( Decode.field "articles" ( Decode.list article ) )


article : Decode.Decoder Article
article =
    Decode.map2 toArticle (Decode.field "title" Decode.string) (Decode.field "story" Decode.string)

toArticle : String -> String -> Article
toArticle t s =
    { title=t, story=s }
```

　このRequestは以下のJson文字列をDecodeします。articleとtoArticleという2つのサブ関数を用いています。JavaScriptではJsonはネイティブなデータ構造なので、そのまま読み込んで使えますが、ElmではJsonをパースして値一つ一つをElm値に変え、Elmのデータ構造に再構築する必要があります。ちょっと面倒ですし、可読性が著しく低下します。JavaScriptなら簡単のに、と思わずつぶやいてしまいますね。

```json:
{
  "articles": [
    {
      "title": "12月29日 晴れ",
      "story": "今日は晴れでした。"
    },
    {
      "title": "12月30日 曇り",
      "story": "今日は大掃除をしました。エアコンの掃除は大変でした。"
    },
    {
      "title": "12月31日 晴れのち雨",
      "story": "今日は大晦日です。夜更かしします。"
    }
  ]
}
```

　最後にタグ一覧と記事一覧を同時に取得するためのCmdの作り方です。Http.getではRequestを作成しますが、これを Http.toTask でTaskに変換します。２つのTaskはTask.map2で1つのTaskに合成できます。これは最初のTaskが終了してから2番目のTaskが走ります。いずれかのTaskが失敗すれば全体が失敗します。以下がそのコードになります。
http://package.elm-lang.org/packages/elm-lang/core/latest/Task

```elm:getTagsArticles 
getTagsArticles : Cmd Msg
getTagsArticles =
    Task.attempt NewTagsArticles ( Task.map2 toModel ( Http.toTask (requestTags) )  ( Http.toTask (requestArticles) ) )


toModel : List Tag -> List Article -> Model
toModel t a =
    { tags=t, articles=a }
```

　この記事は2つの動機から書きました。ひとつはJson.Decodeが面倒だな、自分の頭の中でもう一度整理したいなと思ったからです。２つ目は、RequestとTaskの関係がもやもやしていて、これも整理したいと思ったからです。とりあえずは現状の理解をメモした次第です。