Elm

Portを通したElm Record と JavaScript Object の互換性

Portを通したElm Record と JavaScript Object の互換性を試してみました。

一般的にPortを使った場合、ElmとJavaScriptの間で自動的に以下のようなデータ変換が行われます。JavaScript Interop

Boolean – Elm と JSの両方に型は存在します。
String  – Elm と JSの両方に型は存在します。
Number  – Elm int と float は JS numbersに対応します
List    – Elm List はJS arrayに対応します
Array   – Elm ArrayはJS arrayに対応します
Tuple   – Elm TupleはJS array( fixed-length, mixed-type)に対応します
Record  – Elm RecordはJavaScript objectに対応します。
Maybes  – Elm の Nothing とJust 42 は、JSのnull と 42 に対応します。
Json    – Elm の Json.Encode.Value はJSのJSONに対応します。

 今回はElm Recordが本当にJavaScript objectに対応しているのかを検証してみました。以下のようなElmのレコードを、portでJavaScriptに渡した時、ElmレコードはJavaScript objectに変換されます。JavaScript objectで少しの修正を行い、portを通してElmに戻してみます。

type alias Model =
  { name : String
  , age : Int
  , height : Float
  , married : Bool
  }

 elm2jsとjs2elmという2つのportで以下のような変換が行われます。

           elm2js                    js2elm
Elm record   -->   JavaScript object   -->   Elm record

 実際の実行結果の画面です。ElmのHTMLでname=大谷、age=23、height=193.6と入力すると、Elmレコードが、JavaScript objectとしてJavaScriptに渡され、name=大谷-san、age=23+1、height=193.6+1.5のJavaScript objectに更新されて、Elmに戻されます。Elmレコードが「大谷-san - 24 - 195.1 - True」と表示されていますね。ちなみにmarriedもJavaScriptでFalseからTrueにちゃんと変更されていますね。

image.png

 以下のような流れで、Elmから渡されたレコードが、JavaScriptで更新され、Elmに戻されます。

Elm         {name="大谷",age=23,height=193.6,married=False}  ==>
JavaScript  {name:"大谷",age:23,height:193.6,married:False}  ==>
JavaScript  {name="大谷"+"-san",age=23+1,height=193.6+1.5,married=! False}  ==>
JavaScript  {name:"大谷-san",age:24,height:195.1,married:True}  ==>
Elm         {name="大谷",age=24,height=195.1,married=True}

 それではElmのコードを見てください。

Main.elm
port module PortsTest exposing (..)

import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)

-- MAIN

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

-- MODEL
type alias Model =
  { name : String
  , age : Int
  , height : Float
  , married : Bool
  }

-- MSG

type Msg =
  AddName String
    | AddAge String
    | AddHeight String
    | ToJs
    | FromJs Model

-- INIT

init : (Model, Cmd Msg)
init =
  (Model "" -1 -1.0 False , Cmd.none)

-- UPDATE

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
  case msg of
    AddName txt ->
      ( {model | name = txt }, Cmd.none )

    AddAge txt ->
      ( {model | age = getInt(txt) }, Cmd.none )

    AddHeight txt ->
      ( {model | height = getFloat(txt) }, Cmd.none )

    ToJs ->
      ( model, elm2js model )

    FromJs newmodel ->
      ( newmodel, Cmd.none )



getInt txt =
    case String.toInt(txt) of
        Ok i -> i
        _    -> -1

getFloat txt =
    case String.toFloat(txt) of
        Ok f -> f
        _    -> -1.0


-- OUTGOING PORT

port elm2js : Model -> Cmd msg

-- VIEW

view : Model -> Html Msg
view model =
  div []
      [ input [ placeholder "名前", onInput AddName ] []
      , input [ placeholder "年齢", onInput AddAge ] []
      , input [ placeholder "身長", onInput AddHeight ] []
      , button [ onClick ToJs ] [ text "ToJS" ]
      , div [] [ text model.name
               , text " - "
               , text <| toString model.age
               , text " - "
               , text <| toString model.height
               , text " - "
               , text <| toString model.married
               ]
      ]


-- INCOMING PORT

port js2elm : (Model -> msg) -> Sub msg

-- SUBSCRIPTIONS

subscriptions : Model -> Sub Msg
subscriptions model =
  js2elm FromJs

 JavaScriptのコードは以下の通りです。

index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <script type="text/javascript" src="elm.js"></script>
    </head>
    <body>
    </body>
    <script type="text/javascript">
     function convert(myobject){
         return {
           name: myobject.name + "-san",
           age: myobject.age + 1,
           height: myobject.height + 1.5,
           married: ! myobject.married
         }
     }

     var app = Elm.PortsTest.fullscreen();
        app.ports.elm2js.subscribe(function(myobject) {
         console.log("myobject="+JSON.stringify(myobject));
         var newobject = convert(myobject);
         console.log("newobject="+JSON.stringify(newobject));
         app.ports.js2elm.send(newobject);
     });
    </script>
</html>

 必要なパッケージをインストールします。

elm-package install elm-lang/html

 コンパイルします。

elm-make Main.elm --output elm.js

 サーバを起動します。

elm-reactor -a=www.mypress.jp -p=3030

 ちなみにES6のJavaScript objectのデストラクチャリングを使えば、以下のようにnameだけを受け取るように書くことができます。

index.html
#
        app.ports.elm2js.subscribe(function( {name} ) {
         console.log("name="+name);
#

 またElmからJavaScriptへ、そのまま変換無しでvalue(?)を渡す例は、次の過去記事を参照してください。ElmとJavaScriptの会話(Port) - Qiita

今回は以上です。