ElmでJavascriptの関数を定義する(html to elm to html)


#関数型言語でJSの関数を作りたい
Elm(HaskellライクなAltJS)に興味を持つ人のなかには、Elmでhtmlを生成することに関心がなく、純粋関数型言語のElmで特定の関数を定義したいだけの人もいると思います。まさに私がそうです。これができればHaskellの知識でJavascriptの複雑な関数が作れてしまうからです。また、この方法ならば他のJSライブラリとの連携も容易です。

備忘録も兼ねて、htmlから得られた情報をElmで定義した関数で処理してhtmlに出力する方法のメモを残しておきます。以下の記事の内容は、elm version 0.18.0のものです。

#やりたいこと
つまり、やりたいことは次のplusDefinedInElmやtimesDefinedInElmのような関数の作成です。

```html:index.html
<!DOCTYPE html>
<html>
<head>
    　<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    　<script src="elmFunctions.js"></script>
</head>
<body>
<input type="text" value="5" id="num1"><br>
<input type="text" value="4" id="num2"><br>
<input type="button" id="button4plus" value="plus"><br>
<input type="button" id="button4times" value="times"><br>

Result:<span id="result"></span> 
</body>

<script>
$('#button4plus').on('click',function(){
      var num1 = Number($('#num1').val());
      var num2 = Number($('#num2').val());
      var result= plusDefinedInElm(num1,num2)
  $('#result').html(result);
})
$('#button4times').on('click',function(){
      var num1 = Number($('#num1').val());
      var num2 = Number($('#num2').val());
      var result= tiemsDefinedInElm(num1,num2)
  $('#result').html(result);
})
</script>
</html>
```

```haskell:elmFunctions.js?
plusDefinedInElm : Int->Int->Int
plusDefinedInElm x y = x + y

timesDefinedInElm : Int->Int->Int
timesDefinedInElm x y = x * y
```

このplusというボタンを押した場合、Elmで定義した関数(足し算)の出力がhtmlに表示される(デフォルトの数字の場合、数字の9を表示する)、timesボタンの場合は掛け算の結果(数字の20)が出力されるようにしたいということです。

#Elmのportを使う
純粋関数型言語のElmが外部のファイルとデータの受け渡しをするためにはportという仕組みを使います。portを通してデータを送り、portを通して出力を得ることができます。portでやり取りできる型は限定されていますが[^1]、JSON形式でやり取りできるので自由度は高いです。ここでもあえて出入力をJSON形式にしました。詳細な解説は省き、具体的なコードを見てもらってこれを直接いじるのが一番手っ取り早いと思います。

[^1]: 使える型はStringやIntなど、具体的には[公式サイト](https://guide.elm-lang.org/interop/javascript.html)のCustoms and Border Protectionの項目参照。

###Elm file


```haskell:ElmFunctions.elm
port module ElmFunctions exposing (..)

import Html exposing (Html)
import Json.Encode as Encoder exposing (Value)
import Json.Decode as Decoder exposing (Decoder)
import Platform.Sub

plusDefinedInElm : Int->  Int->  Int
plusDefinedInElm x y = x + y 

timesDefinedInElm : Int->  Int->  Int
timesDefinedInElm x y = x * y

type alias JSON =
   {  num1 : Int,
      num2 : Int
   }

port input1 :(Value -> msg) -> Sub msg
port input2 :(Value -> msg) -> Sub msg

port output1 :Model -> Cmd msg
port output2 :Model -> Cmd msg

-- data from js (input)
decodeJSON : Decoder JSON
decodeJSON =
    Decoder.map2 JSON
        (Decoder.field "num1" Decoder.int)
        (Decoder.field "num2" Decoder.int)

-- MODEL (output)
type alias Model =
    { result : Int
    , calMethod : String
    }

-- initmodel
initModel : ( Model, Cmd Msg )
initModel = ( Model 0 ""
            , Cmd.none
            )

-- UPDATE
type Msg =    JsonFromJS1 Value
            | JsonFromJS2 Value

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = case msg of
        JsonFromJS1 json -> 
                let 
                    decodedJSON  =Decoder.decodeValue decodeJSON json 
                    decodedJSON2 = case decodedJSON of 
                        Ok a -> a
                        Err errorMsg-> Debug.crash ("error in update(1):" ++ errorMsg)
                    newModel=  { result=plusDefinedInElm decodedJSON2.num1 decodedJSON2.num2,
                                 calMethod= "plus"
                                }
                in 
                    newModel ! [ output1 newModel ]
 
        JsonFromJS2 json -> 
                let 
                    decodedJSON  =Decoder.decodeValue decodeJSON json 
                    decodedJSON2 = case decodedJSON of 
                        Ok a -> a
                        Err errorMsg-> Debug.crash ("error in update (2):" ++ errorMsg)
                    newModel=  { result=timesDefinedInElm  decodedJSON2.num1 decodedJSON2.num2,
                                 calMethod= "times"
                                }
                in 
                    newModel ! [ output2 newModel ]
 
-- VIEW
view : Model -> Html Msg
view model = Html.h1 [] [Html.text "Elm Port Test" ]

-- SUBSCRIPTIONS
subscriptions model = Platform.Sub.batch
                    [ input1 JsonFromJS1
                    , input2 JsonFromJS2
                    ]
-- main
main = Html.program
        { init = initModel
        , view = view
        , update = update
        , subscriptions = subscriptions
        }
```
この記事の趣旨的にHTMLの表示(view)は何もなくてもいいかもしれませんが、せっかくなのでタイトルだけ表示してみました。そして、このElmのファイルを

```sh:terminal
elm-make ElmFunctions.elm --output=elmFunctions.js
```

でjavascriptに変換します。そして、次のようなhtmlでElmの関数が使えます。
###html file

```html:index.html
<!DOCTYPE html>
<html>
<head>
  <title>Elm Port Test</title>
    　<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    　<script src="elmFunctions.js"></script>
</head>
<body>
<div id="main_test"></div>

<input type="text" value="5" id="num1"><br>
<input type="text" value="4" id="num2"><br>
<input type="button" id="button4plus" value="plus"><br>
<input type="button" id="button4times" value="times"><br>

Result <span id="result"></span> 
By <span id="calMethod"></span> 
</body>

<script>
  
var elm_node = document.getElementById('main_test');
var app = Elm.ElmFunctions.embed(elm_node);


$('#button4plus').on('click',function(){
      var num1 = $('#num1').val();
      var num2 = $('#num2').val();
      var sendingJSON ={
                num1 : Number(num1),
                num2 : Number(num2)
              }
    app.ports.input1.send(sendingJSON); // sending JSON data to Elm
})
app.ports.output1.subscribe(function (model) { //result from Elm
  var result = model.result;
  var calMethod = model.calMethod;
  $('#result').html(result);
  $('#calMethod').html(calMethod);
})

$('#button4times').on('click',function(){
      var num1 = $('#num1').val();
      var num2 = $('#num2').val();
      var sendingJSON ={
                num1 : Number(num1),
                num2 : Number(num2)
              }
    app.ports.input2.send(sendingJSON); // sending JSON data to Elm
})
app.ports.output2.subscribe(function (model) { //result from Elm
  var result = model.result;
  var calMethod = model.calMethod;
  $('#result').html(result);
  $('#calMethod').html(calMethod);
})
</script>
</html>
```

注意すべきなのは、Elmに送る関数のカッコの外

```
$('#button4plus').on('click',function(){
    ...
    app.ports.input1.send(sendingJSON);
})

```

でElmの出力結果を処理する関数

```
app.ports.output1.subscribe(function (model) {
...
})
```

を書くことかもしれません。これで関数型言語の知識でJavascriptが書け、関数型フリークがフロントエンド開発に手軽に参加できます。

##参考サイト
- [ElmのPortでJSを使う](https://qiita.com/jooex/items/5ff2d3b86563cf5dbd84)
- [An introduction to ElmのJavaScript Interop](https://guide.elm-lang.org/interop/javascript.html)