🐓

Elmのデータの実行時表現を調べてみよう

2021/05/08に公開

ElmはJavaScriptへとコンパイルされてJavaScript環境で実行されるわけですが、実行時のデータの表現はどうなっているのかや、関数はどのような名前でマングリングされるのだろうという素朴な疑問をもったので、調べてみました。やりかたは、単純に各種のデータを変数で定義しコンパイルして実行し、DevToolsで実行時の値を覗いてみるだけです。

Elmソースコード(抜粋)

int =
    42

float =
    3.14

string =
    "Hello, World!"

bool =
    True

nothing =
    Nothing

just =
    Just 42

ok =
    Ok 42

err =
    Err "Failed"

unit =
    ()

tuple =
    ( 1, "a" )

tuple3 =
    ( 2, "b", False )

object =
    { familyName = "Doe", personalName = "John", phone = "090-0000-0000" }

list =
    [ 0, 1, 2 ]

array_empty =
    Array.empty

array =
    Array.fromList [ "a", "b", "c" ]

arrays =
    Array.append (Array.fromList [ "a", "b", "c" ]) (Array.fromList [ "d", "e", "f" ])

set_empty =
    Set.empty

set =
    Set.fromList [ 1, 2, 3, 4, 5 ]
    
variant_leaf =
    Leaf 42

variant_branch =
    Branch (Leaf 0) (Leaf 1)

dict_empty =
    Dict.empty

dict =
    Dict.fromList [ ( "a", 1 ), ( "b", 2 ), ( "c", 3 ) ]
type Tree a
    = Leaf a
    | Branch (Tree a) (Tree a)

コンパイル後のJavaScript

var variant_leaf = $author$project$Main$Leaf(42);
var variant_branch = A2(
    $author$project$Main$Branch,
    $author$project$Main$Leaf(0),
    $author$project$Main$Leaf(1)
);
var unit = _Utils_Tuple0;
var tuple3 = _Utils_Tuple3(2, "b", false);
var tuple = _Utils_Tuple2(1, "a");
var sub_none = $elm$core$Platform$Sub$none;
var string = "Hello, World!";
var set_empty = $elm$core$Set$empty;
var set = $elm$core$Set$fromList(_List_fromArray([1, 2, 3, 4, 5]));
var ok = $elm$core$Result$Ok(42);
var object = { familyName: "Doe", personalName: "John", phone: "090-0000-0000" };
var nothing = $elm$core$Maybe$Nothing;
var list = _List_fromArray([0, 1, 2]);
var just = $elm$core$Maybe$Just(42);
var _int = 42;
var _float = 3.14;
var err = $elm$core$Result$Err("Failed");
var dict_empty = $elm$core$Dict$empty;
var dict = $elm$core$Dict$fromList(
_List_fromArray(
[
    _Utils_Tuple2('a', 1),
    _Utils_Tuple2('b', 2),
    _Utils_Tuple2('c', 3)
]));
var bool = true;
var arrays = A2(
    $elm$core$Array$append,
    $elm$core$Array$fromList(_List_fromArray(["a", "b", "c"])),
    $elm$core$Array$fromList(_List_fromArray(["d", "e", "f"]))
);
var array_empty = $elm$core$Array$empty;
var array = $elm$core$Array$fromList(_List_fromArray(["a", "b", "c"]));

実行時の値

array: {$: "Array_elm_builtin", a: 3, b: 5, c: Array(0), d: Array(3)}
array_empty: {$: "Array_elm_builtin", a: 0, b: 5, c: Array(0), d: Array(0)}
arrays: {$: "Array_elm_builtin", a: 6, b: 5, c: Array(0), d: Array(6)}
bool: true
dict: {$: "RBNode_elm_builtin", a: {}, b: "b", c: 2, d: {},}
dict_empty: {$: "RBEmpty_elm_builtin"}
err: {$: "Err", a: "Failed"}
just: {$: "Just", a: 42}
list: {$: "::", a: 0, b: {}}
nothing: {$: "Nothing"}
object: {familyName: "Doe", personalName: "John", phone: "090-0000-0000"}
ok: {$: "Ok", a: 42}
set: {$: "Set_elm_builtin", a: {}}
set_empty: {$: "Set_elm_builtin", a: {}}
string: "Hello, World!"
tuple: {$: "#2", a: 1, b: "a"}
tuple3: {$: "#3", a: 2, b: "b", c: false}
unit: {$: "#0"}
variant_branch: {$: "Branch", a: {}, b: {}}
variant_leaf: {$: "Leaf", a: 42}
_float: 3.14
_int: 42

まとめ

  • プリミティブな値は素直にJavaScriptのプリミティブな値に変換されています
  • レコードはそのままJavaScriptのオブジェクトに変換されています
  • カスタム型の値は、 {$: "Leaf", a: 42} のようにオブジェクトに変換されています。ヴァリアント名は$という名前のプロパティで、それからヴァリアントのフィールドは先頭からab……とアルファベット小文字の順でプロパティ名が与えられるようです
  • タプルは{$: "#2", a: 1, b: "a"}のように#2というタグがついたオブジェクトでした。_Utils_Tuple2みたいなユーティリティ関数で簡単に作れるようになっているようです
  • NothingJustOkのような組み込みのカスタム型も同様でした
  • ヴァリアントは$author$project$Main$Leafというように$author$project$モジュール名$ヴァリアント名という命名規則で定義された関数となっているようです
  • Set$: "Set_elm_builtin"なオブジェクトで、実装は赤黒木みたいです
  • Dictも赤黒木みたいです
  • Listは他のカスタム型と同じように $: "::"とかプロパティを持つオブジェクトですが、_List_fromArrayでJavaScriptの配列から簡単に作れるようです
  • ArrayArray_elm_builtinというヴァリアント名がつけられたオブジェクトになっています。aが長さで、実際のデータはdに生のJavaScriptの配列として格納されているようです。bcはよくわかりませんでした
  • ちなみに各関数はカリー化されていて、呼び出すにはA3のような関数を使います

感想

データはわりと素直なJSONオブジェクトですし、関数はカリー化されているものの素直でユニークな名前がついたただの関数で、JavaScriptからでも扱いやすそうだなあと思いました。

次のお話: ElmでForeign Function Interface (強引)

Discussion