Elmでの式の組み合わせ方を紹介します。
計算の結果を元に、次の計算を実行する。
Int -> Intな関数 f, g の二つがあるとします。
gは、f 42の結果を引数として受けとりたいとき次のように式を組み合わせます。
g (f 42)
これは次にように解釈できます。
-
f関数に42を渡す。 - その結果を
gに渡す。
こういった計算の組み合わせ方を逐次実行と呼びます。
この例の()は、必須です。もし() を消して次のように書いてしまったとしましょう。
g f 42 -- 引数の数があっていない。
これは、g 関数に f と 42 の二つの値を渡すことになってしまいます。
そして g 関数は、 Int -> Int なのでこれは、引数の数があっていないというエラーです。
順番として、fの次にgを呼び出すので、タイピングする順番と逆です。これでは不便なので、**パイプライン演算子(|>)**というものを使ってタイピングする順番と呼び出される順番を合わせることができます。
このパイプライン演算子(|>)は、記号の▷に見えますね。左から右に流れることがわかりやすい記法です。
次の式は、g (f 42)と同じです。
f 42
|> g
複数の要素に対して同じ関数で計算する。
単一の値であれば、()やパイプライン演算子(|>)で、式を組み合わせられることがわかりました。
では、複数の値のときどのように式を組み合わせるか考えてみます。
複数の値を扱う典型例は、リストです。
[ 0, 1, 2 ]
Int -> Int型である関数fがあったとして、その関数にリストを渡すそうと思っても、次のような式はエラーになってしまいます。
[ 0, 1, 2 ]
|> f
IntとList Intは、異なる型だからです。
List.map 関数を使うと関数fにList Intを渡せるようになります。
[ 0, 1, 2 ]
|> List.map f
そして、この例の結果は、関数fにリストの要素それぞれを渡した結果のリストです。
エラーが起こりうる式を組み合わせて、エラー時の処理をする。
エラーが起こりうる関数の戻り値型は、Maybe a型で表現できます。
たとえば、文字列から数値へ変換する関数 String.toIntは、 String -> Maybe Int型です。
String.toInt "42"
この例では、渡す引数を"42"と指定しているので、数値への変換に失敗することはありませんが、もし、ユーザーの入力値であれば、数値への変換に失敗することがあります。
数値への変換に成功したところで、まだ安心はできません。あなたが書いたアプリケーションでは、この数値を日時の月に変換したいかもしれません。その成功する入力値は、"1" から "12"という数値のみです。
月に変換する関数は、失敗しうるのでInt -> Maybe Monthという型にしてmonthFromInt関数を定義します。
monthFromInt : Int -> Maybe Month
monthFromInt n =
case n of
1 -> Just Jan
2 -> Just Feb
-- 3月から12月まで書く
_ -> Nothing
String.toInt 関数も上記で定義したmonthFromInt関数も、どちらも失敗しうる関数です。このような関数は、 Maybe.andThen 関数を使うことで組み合わせられます。
String.toInt 42
|> Maybe.andThen monthFromInt
「and then」という英文は、日本語訳で「それから」という意味です。「もしこの式に成功したら、次を実行」のような式の組み合せとして読めそうですね。
Maybe.andThenを使って繋げた式は、二つの式が成功したときだけ成功を示すJust xが手に入ります。どちらか一つでも失敗したら、結果は、Nothingです。
副作用をもたらす式をElmから分離する。
Elmは、純粋関数型言語なので、副作用を起こす処理を式として書けません。たとえば現在時刻を取得する処理は、副作用があります。なので式として書くのではなく、Runtime Systemに結果をもってきてもらいます。このようにRuntime Systemに結果を持ってきてほしい式は、Task x aという型になります。
Task x aのaは、Runtime Systemが、実行した計算結果の型になる型引数です。現在時刻の取得であれば、現在時刻を表す型( Posix )になります。The Elm Architectureの update関数は、この計算結果aの値をxでタグ付けられたメッセージとして受けとります。
Runtime Systemの力を借りずにElm内でTask x aからa(副作用のある計算結果)を取り出すことはできません。この制約のおかげで、Elmの式は、参照透過性があると言えます。
しかし、Task x aという型のままでは、通常の逐次実行の記法で他の式と組み合わせることができません。たとえば、aがTime.now関数によって得られる計算結果 Posix型だったとしましょう。
Time.now
この式の型は、Task x Posixとなります。
この現在時刻を取得する式を加工して、現在時刻から時間(0時~23時)だけを取り出す GotHour にしてみます。
Time.now
|> Time.map (Time.toHour Time.Utc) -- パイプラインの次にTime.mapを間に挟む。
Time.nowの型は、Task x Posixです。Time.toHour Time.Utcの型は、Posix -> Int なのでそのままだと Task x Posixを渡せません。Time.map は、Posix -> Intを Task x Posixを受け取れるように加工してくれます。
このように、副作用をもたらす処理をRuntime Systemに処理してもらうなモジュールには、map関数が用意されていて、計算結果を得ずとも値を加工できます。
まとめ
式の組み合わせ方をいくつか、紹介しました。プログラミングにおいて、リスト処理やエラー処理は、面倒な作業の一つですが、Elmでは、式の組み合わせ方を少し変えるだけで、様々な処理を短い式で記述できます。