正月にElm触りだしてつまずいたこと

# この記事について
正月にElmを使って自分用のブログのクライアントサイドを作っていたのですが、つまずいたことを備忘録として書いておきます。

なお、[公式ガイド](https://guide.elm-lang.org/) ([日本語版はこちら](https://guide.elm-lang.jp/))をサーッと目を通してから触り始めてつまずいたことを書いているので、とりあえずそちらを読んだ方を想定しています。

ちなみに、私はReactとかはちょこっと触っていてなにか作ったことはあるものの、Reduxとか使ってまじめな開発はやっていないという感じのレベルです。

# 開発環境をどう作るべきか？
@ababup1192さんのこちらの記事をほぼ丸パクリさせてもらいました。
https://gist.github.com/ababup1192/a1a091bcc0e535d180544639f531302c

最初elm reactorとか使って開発していたのですが、いざJSにコンパイルして、html内に読み込ませて、ほんでcssも読み込ませて―とか考えだすとやっぱりwebpackとか使ったほうがよいですね。

ちなみに、@ababup1192さん曰く、開発環境のベースリポジトリとしては最近はこちらの方がおすすめとのことでした(情報提供ありがとうございます！)。
https://github.com/simonh1000/elm-webpack-starter

<blockquote class="twitter-tweet" data-conversation="none" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://t.co/lv0uZBTylH">https://t.co/lv0uZBTylH</a><br><br>最近はこっちに乗り換えてしまいました・・・。<br>elm-dev-env だと、コンパイルがなぜか激重なので、こちらに乗り換えてください！使い勝手ほぼ一緒です！</p>&mdash; ABAB↑↓BA (@ababupdownba) <a href="https://twitter.com/ababupdownba/status/1081499535896002561?ref_src=twsrc%5Etfw">2019年1月5日</a></blockquote>

# Navigationでページ遷移を実装
[公式ガイドのナビゲーションする](https://guide.elm-lang.jp/webapps/navigation.html)を見ながら、ページ遷移を作って行きました。
が、公式ガイドの例にはURLによってViewを切り替える例がなかったため、小一時間どうやるべきなのか考えてしまいました。

とりあえず公式ガイドを読みなおし、[URLをパースする](https://guide.elm-lang.jp/webapps/url_parsing.html)の統合の章に書いてある内容を参考に実装していきました。

あと、[elm-spa-example](https://github.com/rtfeldman/elm-spa-example/blob/b5064c6ef0fde3395a7299f238acf68f93e71d03/src/Route.elm)のソースコードも参考にさせてもらいました。

どうやら、UrlからRoute型のvariantsを作って、Modelにもたせてやればよさそうな感じがしたので、こんな感じの実装になりました。

```elm
-- URL PARSER
type Route
    = TopPage
    | NextPage String

routeParser : Parser (Route -> a) a
routeParser = oneOf [ map NextPage (s "next" </> string) ]

parseUrl : Url.Url -> Route
parseUrl url =
    let
        parsed =
            -- #をパースするためにUrlをparseする前にfragmentの設定してる
            -- https://package.elm-lang.org/packages/elm/url/1.0.0/Url
            -- 以下の実装を参考にした
            -- https://github.com/rtfeldman/elm-spa-example/blob/b5064c6ef0fde3395a7299f238acf68f93e71d03/src/Route.elm#L59
            { url | path = Maybe.withDefault "" url.fragment, fragment = Nothing }
                |> parse routeParser
    in
    case parsed of
        Just route -> route
        Nothing -> TopPage


-- UPDATE
type Msg
    = LinkClicked Browser.UrlRequest
    | UrlChanged Url.Url

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        LinkClicked urlRequest ->
            case urlRequest of
                Browser.Internal url ->
                    ( model, Nav.pushUrl model.key (Url.toString url) )

                Browser.External href ->
                    ( model, Nav.load href )

        UrlChanged url ->
            let
                -- Urlが変更されたら、parseUrlでRoute型variantsへ変換
                route = parseUrl url
            in
            ( { model | url = url, route = route }, Cmd.none )


-- VIEW
view : Model -> Browser.Document Msg
view model =
    case model.route of
        TopPage ->
            { title = "URL Interceptor"
            , body =
                let
                    contentPath = "/#/next/"
                in
                [ text "The current URL is: "
                , b [] [ text (Url.toString model.url) ]
                , ul []
                    [ viewLink <| contentPath ++ "foo"
                    , viewLink <| contentPath ++ "bar"
                    ]
                ]
            }
        NextPage param ->
            { title = "URL Interceptor"
            , body = [ text param ]
            }
```
                
コードの全体はGistに貼っておきます。
https://gist.github.com/yuizho/c0b08ee59d3da81891426b17209fff4d


# トップがListのJsonをDecodeする
トップレベルがリストのJsonをどうDecodeするのかでちょっと悩みましたが、
[Decode.list](https://package.elm-lang.org/packages/elm-lang/core/5.0.0/Json-Decode#list)を使えば一発でした。

たとえば、こんなJsonをデコードしたかったら、

```json
[
    {
        "title": "タイトル",
        "id": 1,
    },
    {
        "title": "タイトル2",
        "id": 2,
    },
]
```

こんなかんじですね。

```elm
type alias Article =
    { title : String
    , id : Int
    }

articlesDecorder : Decode.Decoder (List Article)
articlesDecorder =
    Decode.list
        (Decode.map2 Article
            (Decode.field "title" Decode.string)
            (Decode.field "id" Decode.int)
        )
```

# ページを初期化するために複数のAPIをたたきたい
一つのAPIをたたいてその結果を元にModelを更新する方法は公式ガイドに書いてあったのでスムーズに実装できました。
が、複数のAPIをたたき、その複数の結果を同期してModelを更新する方法がよくわかりませんでした。

いろいろ漁っているとどうやら、Taskを使うとJavaScriptのPromise.allみたいな感じで複数の非同期処理の結果を同期して取得できるらしい。
https://www.reddit.com/r/elm/comments/91t937/is_it_possible_to_make_multiple_http_requests_in/e30vlns/

ただ、これをHttp.getとかに適用する方法がよくわからない……

そんなときに[Http.toTask](https://package.elm-lang.org/packages/elm/http/1.0.0/Http#toTask)というものがあることを発見しました。
こんな感じでHttp.getの結果を食わしてやれば、前述のredditのサンプルコードと同様にタスクとして[Task.map2](https://package.elm-lang.org/packages/elm-lang/core/latest/Task#map2)へ渡すことが出来た。

```elm
Http.get articleUrl articleDecorder |> Http.toTask
```

全体像としてはこんな感じ。

```elm
-- UPDATE


type Msg
    = ShowContent (Result Http.Error ( Article, String ))


-- ...

-- HTTP
fetchContent : String -> Cmd Msg
fetchContent id =
    let
        articleTask =
            Http.get articleUrl articleDecorder |> Http.toTask

        contentTask =
            Http.getString contentUrl |> Http.toTask
    in
    Task.attempt ShowContent <|
        Task.map2 (\article content -> ( article, content )) articleTask contentTask
```

ちなみに、これ実装するのに参考にしたソースが数ヶ月前にcloneした[公式ガイドにのっていたサンプルコード](https://github.com/evancz/elm-architecture-tutorial/)だったため、使ったHttpのバージョンが古いです(1.0.0。いまは2.0.0がでている)。

多分2.0.0でも似たような感じで同様のことができるんだろうと思います。

# おわりに
これはElmに限ったことでは無いと思うのですが、公式ガイドに書いてあることは真似して実装できるがそれ以外のことはすべてつまずくみたいな感じでした(やり方がわかってみれば、なーんだ簡単じゃんって感じなんですが)。もっと触りつづけよう。
またなにか増えたら書いていこうと思います。

一週間程度Elmを触ってみて、Elmはコンパイルが通るまではなかなか大変だけど、一度コンパイルが通ってしまえばほぼ考えた通りに動くという感じでなかなか心地よいです。

まだテストコードとか書いてないんですが、そんなに一生懸命テストを書かなくても大丈夫そうだなぁという気持ちです。

しばらく触ってみて、この辺の感想が変わってきたら追記しようと思います。