ElmでWebSocketを試してみた(Sub)


　Elmは自分でできない汚れ仕事をElm Realtimeに下請けさせますが、その時のインターフェースとしてCmd/Subを使います。Subのexampleとして公式サイトに載っているWebSocketのプログラムを試しましたのでその記録です。
https://guide.elm-lang.org/architecture/effects/web_sockets.html

# １．開発環境

　最後にリストしますが、index.htmlとMyWebSocket.elmのファイルを用意します。開発の流れは以下の記事に従います。
[Elm開発環境について - Qiita](https://qiita.com/sand/items/9afaef65c6f1ebf453da)

以下のコマンドを実行します。

```sh
mkdir websocket
elm-package install elm-lang/websocket
```
　index.htmlとMyWebSocket.elmをwebsocketディレクトリにおいてコンパイルします。elm-reactorを実行するとブラウザからアクセスできます。

```sh
elm-make MyWebSocket.elm --output elm.js
elm-reactor -a=www.xxxxx.jp -p=3030
```


# ２．index.html

　コンパイル結果のelm.jsを読み込んで、そこからimportしたMyWebSocketモジュールを使って、Elm.MyWebSocket.embedでアプリを走らせています。

```html:index.html
<!doctype html>
<html>
    <head>
    </head>
    <body>
        <div id="elm-area"></div>
        <script src="elm.js"></script>
        <script>
            Elm.MyWebSocket.embed(document.getElementById("elm-area"));
        </script>
    </body>
</html>
```

# ３．MyWebSocket.elm

　Elmプログラムの全てですが、大変シンプルなことに驚きます。以下に多少の説明を加えたいと思います。

```elm:MyWebSocket.elm
module MyWebSocket exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import WebSocket

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

-- MODEL
type alias Model =
  { input : String
  , messages : List String
  }


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


-- UPDATE
type Msg
  = Input String
  | Send
  | NewMessage String


update : Msg -> Model -> (Model, Cmd Msg)
update msg {input, messages} =
  case msg of
    Input newInput ->
      (Model newInput messages, Cmd.none)

    Send ->
      (Model "" messages, WebSocket.send "ws://echo.websocket.org" input)

    NewMessage str ->
      (Model input (str :: messages), Cmd.none)

-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
  WebSocket.listen "ws://echo.websocket.org" NewMessage

-- VIEW
view : Model -> Html Msg
view model =
  div []
    [ div [] (List.map viewMessage model.messages)
    , input [onInput Input] []
    , button [onClick Send] [text "Send"]
    ]

viewMessage : String -> Html msg
viewMessage msg =
  div [] [ text msg ]
```

　index.htmlで使われるのでプログラムのモジュール名を宣言しておく必要があります。

```elm:モジュール名宣言
module MyWebSocket exposing (..)
```


　まずWebSocketの関連部分は以下のとおりです。これはelm-lang/websocketモジュールで提供される機能です。

```elm:elmのWebSocket関連部分
WebSocket.send "ws://echo.websocket.org" input
WebSocket.listen "ws://echo.websocket.org" NewMessage
```

　ws プロトコルは、WebSocket サーバとリソース名を識別する。exampleですので、ここではecho.websocket.orgという既存のエコーサーバが使われています。ちなみに通信がTLS により保護されることが必要なときはwssが使われます。
https://triple-underscore.github.io/RFC6455-ja.html#section-11.1.1

　それぞれの型を以下に挙げると、使われ方もはっきりすると思われます。WebSocket.send はupdateの返り値のCmd部分に使われます。WebSocket.listenはsubscriptionsの定義に使われます。

```elm:WebSocket関数の型
WebSocket.send : String -> String -> Cmd msg
WebSocket.listen : String -> (String -> msg) -> Sub msg
```

　subscriptions関数はイベントのリスナーのようなものを設置するためのものです。今回はWebSocketサーバ（echo.websocket.org）からの通信をリッスンします。通信を受け取るとNewMessage strという型でupdate関数が呼ばれます。NewMessageはlisten関数の第2引数で、strが通信内容ですね。

```elm:subscriptions関数
subscriptions : Model -> Sub Msg
subscriptions model =
  WebSocket.listen "ws://echo.websocket.org" NewMessage
```

　以上ですが、WebSocket モジュールがうまくCmd/Subのアーキテクチャにはまって、全体的にシンプルなプログラムになっていると思います。