ElmのNative moduleを書く

  • 18
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

Elmとjavascriptのやりとりには、どちらかと言えば高レベルなportを使用するものの他に、
低レベルで、PureScript等で言うffiに相当する、Nativeモジュールという機能が有ります。

Elmのcoreモジュールやelm-htmlなど他のjavascriptライブラリとのやりとりにはこれを使っています。

Elmはまだ発展途上でcoreモジュールに足りない機能も多々有るので、Nativeモジュールは書ける方が良いでしょう。

本稿では現時点でcoreモジュールに無い、takeWhileの実装を通じてNativeモジュールの書きかたを解説します。

開発開始

なにはともあれelm-makeコマンドを使ってプロジェクトの雛形を作りましょう。

$ elm-make
Some new packages are needed. Here is the upgrade plan.

  Install:
    elm-lang/core 1.0.0

Do you approve of this plan? (y/n) y
Downloading elm-lang/core
Packages configured successfully!
Compiled 32 files

javascriptを書く

まずjavascriptを書きましょう。
NativeモジュールはNative以下に配置する様に決まっているので、今回はNative.ListProcとでもしておきましょう。以下が雛形となります。

Native/ListProc.js
Elm.Native.ListProc = {};

Elm.Native.ListProc.make = function(elm) {
    elm.Native = elm.Native || {};
    elm.Native.ListProc = elm.Native.ListProc || {};
    if (elm.Native.ListProc.values) return elm.Native.ListProc.values;

    /* ここに実装を書く */

    return elm.Native.ListProc.values = {
        /* ここにエクスポートする関数を列挙する */
    };
};

log関数を書く

いきなり2引数かつ高階関数は難易度が高いので、引数をconsoleに出力して、そのまま返すlog : String -> String関数を書いてみましょう。

まずは実装部分です

log.js
    function log (x) {
        console.log(x);
        return x;
    }

これはまぁ自明ですね。これをエクスポートしましょう。

export.js
    return elm.Native.ListProc.values = {
        log: log
    };

これでlog関数についてのjavascript部分の記述は完了しました。

ラッパーを書く

これをラップして型安全にするelmのモジュールを書きましょう。

ListProc.elm
module ListProc where

import Native.ListProc

log : String -> String
log = Native.ListProc.log

何も解説する事は無いかと思います。適切な型を教えてあげるだけでOKです。

main

このlog関数を使用するmain関数を定義して実行してみましょう。

main.elm
import ListProc
import Text(plainText)

main = plainText <| ListProc.log "Hello, World!"
$ elm-make main.elm --output main.html
Error when searching for modules imported by module 'ListProc':
    Could not find module 'Native.ListProc'

Potential problems could be:
  * Misspelled the module name
  * Need to add a source directory or new dependency to elm-package.json

警告が出ました。どうやらNativeモジュールを使用するにはelm-package.jsonで宣言する必要がある様です。

elm-package.jsonを以下の様に変更しましょう。

elm-package.json.diff
--- elm-package.json.bak    2014-12-12 23:40:42.000000000 +0900
+++ elm-package.json    2014-12-12 23:40:54.000000000 +0900
@@ -7,7 +7,8 @@
         "."
     ],
     "exposed-modules": [],
+    "native-modules": true,
     "dependencies": {
         "elm-lang/core": "1.0.0 <= v < 2.0.0"
     }
}
$ elm-make main.elm --output main.html
Compiled 2 files
Successfully generated main.html

無事コンパイル出来たのでmain.htmlを開き、Hello, World!と表示されるとともに、consoleに出力されている事を確認してください。

さて、これでとりあえずNativeモジュールの書きかたは解ったので、サクっとtakeWhileを実装しましょう。

takeWhileString

ひとまずStringのみを取るtakeWhileString : (Char -> Bool) -> String -> Stringを書きましょう

まずは実装部分です。

takeWhileString.js
    // 別のモジュールを使用する時はこの様にする。
    // Elm.Native.UtilsにはElmの型を構築する関数などが入っている。
    var Utils = Elm.Native.Utils.make(elm);

    function takeWhileString(p, l) {
      for(var i = 0; i < l.length; i++) {

        // Utils.chrを用いてChar型にする。
        // 1引数関数にはそのまま引数を渡せばOK
        // 2引数以上ならA2(f, a1, a2)の様にA?関数に渡す。
        if(!p(Utils.chr(l[i]))) {
          return l.slice(0,i);
        }
      }
      return l;
    }

次にこれをエクスポートする記述を足しましょう。

export.js
    return elm.Native.ListProc.values = {
        // 2引数以上の時はF?関数で包む。
        takeWhileString: F2(takeWhileString)
    };

そしてラッパーを書いて、

ListProc.elm
module ListProc where

import Native.ListProc

takeWhileString : (Char -> Bool) -> String -> String
takeWhileString = Native.ListProc.takeWhileString

メインを変更してテストしましょう。

main.elm
import ListProc
import Text(plainText)

main = plainText <| ListProc.takeWhileString ((/=) ' ') "Hello, World!"
$ elm-make main.elm --output test.html
Compiled 1 file
Successfully generated test.html

生成されたtest.htmlを開くと"Hello,"と表示されると思います。これでtakeWhileStringの実装が完了しました。

Listに対する実装は割愛させていただきます。
Elmの多相型にはnumber(Int,Float)/comparable(Int,Float,Char,String,lists,tuples)/appendable(String,List,Text)の3種類があります。
takeWhileの場合appendableを取る様に出来そうですが、エラーメッセージが固定で(++)がどうこうと出るので、特段の理由が無い限りは多相型としない方が良さそうです。
List.takeWhile, String.takeWhileの様にモジュールで分けるべきでしょう。

おわりに

以上、駆け足ではありますが、elmのNativeモジュールの書きかたをざっと流しました。
インラインでffiを書けるpurescript等の様な手軽さは有りませんが、jsとして分離するのでエディタの支援が容易に受けられる、他のaltJSで記述できる、などの利点があると思います。

足りない機能はどんどんNativeモジュールで実装しましょう!!

今回のコードはこちらにまとめてあります。

この投稿は Elm Advent Calendar 201412日目の記事です。