関数の基本
この章では、Elmに慣れるために重要な基本的な構文、関数、関数シグネチャ、部分適用、およびパイプ演算子について説明します。
機能
Elmは2種類の関数をサポートしています:
- 無名関数
- 名前付き関数
無名関数
無名関数は、その名前が示すように、名前なしで作成する関数です。
\x -> x + 1
\x y -> x + y
バックスラッシュと矢印の間には関数の引数を列挙し、矢印の右側にはそれらの引数で何をすべきかを示します。
名前付き関数
Elmの名前付き関数は次のようになります。
add1 : Int -> Int
add1 x =
x + 1
- この例の最初の行は関数のシグネチャです。シグネチャはElmではオプションですが、関数の意図を明確にするために記述することが推奨されています。
- 残りは関数の実装です。実装は、その直上で定義したシグネチャに従わなければなりません。
この場合、シグネチャは「引数として整数(Int)をとり、別の整数(Int)を返す」ということを言っています。
この関数は、以下のように呼び出すことができます:
add1 3
Elmでは、関数適用を表すために(カッコの代わりに)空白を使用します。
以下は別の名前付き関数です:
add : Int -> Int -> Int
add x y =
x + y
この関数は両方ともIntである2つの引数をとり、別のIntを返します。この関数は次のように呼び出すことができます。
add 2 3
引数なし
Elmでは定数は引数をとらない関数です。
name =
"Sam"
どのように関数適用がなされるか
前述のように、2つの引数をとる関数は次のようになります。
divide : Float -> Float -> Float
divide x y =
x / y
このシグネチャを、2つの浮動小数点数をとり、別の浮動小数点数を返す関数と考えることもできます。
divide 5 2 == 2.5
しかし、これはまったく真実ではありません。Elmでは、すべての関数はただ1つの引数をとり、1つの結果を返します。ここではその結果は別の関数になります。 上記の関数を使って説明しましょう。
-- 次のようにすると:
divide 5 2
-- 次のように評価されます:
((divide 5) 2)
-- 最初の「divide 5」が評価されます。
-- 引数 '5'は `divide`に適用され、中間的な関数になります。
divide 5 -- ->中間的な関数
-- この中間的な関数を `divide5`と呼びます。
-- この中間的な関数のシグネチャと本体を見ることができたなら、次のようになるでしょう:
divide5 : Float -> Float
divide5 y =
5 / y
-- したがって、すでに `5`が適用された関数があります。
-- 次の引数、すなわち `2`が適用されます
divide5 2
-- これは最終結果を返します
括弧を書かないで済むのは、関数適用が左結合だからです。
カッコでグループ化する
別の関数呼び出しの結果で関数を呼び出す場合は、呼び出しをグループ化するために括弧を使用する必要があります。
add 1 (divide 12 3)
ここでは、「add」の第2引数として「divide 12 3」の結果が与えられます。
対照的に、他の多くの言語では、次のように書かれます。
add(1, divide(12, 3))
部分適用
上で説明したように、すべての関数は1つの引数しか取らず、別の関数または結果を返します。
これが意味するのは、引数が1つだけでも上記の add
のような関数を呼び出すことができるということです。たとえばadd 2
とすると、部分的に適用された関数が返ります。
この結果の関数のシグネチャは、Int -> Int
です。
add 2
は最初引数が値'2'に束縛された別の関数を返します。返ってきた関数に2番目の引数を与えて呼び出すと、その結果は「2 + 2番目の引数」になります。
add2 = add 2
add2 3 -- 結果は5
部分適用は、コードを読みやすくしたり、アプリケーション内の関数間で状態を渡したりすることができ、Elmプログラミングにおいて非常に便利です。
パイプ演算子
上記のように、次のような関数を入れ子にすることができます:
add 1 (multiply 2 3)
これは簡単な例ですが、もっと複雑な例を考えてみましょう:
sum (filter (isOver 100) (map getCost records))
このコードが読み難いのは、内から外に解決していくからです。パイプ演算子を使用すると、そのような式をより読みやすく書くことができます。
3
|> multiply 2
|> add 1
このようにできるのは前述の部分適用のおかげです。この例では、値「3」が部分適用された関数multiply 2
に渡されます。その結果は、部分適用されたまた別の関数 add 1
に渡されます。
パイプ演算子を使用すると、上記の複雑な例は次のようになります。
records
|> map getCost
|> filter (isOver 100)
|> sum