関数
💡 お知らせ: このドキュメントはAIによって翻訳されています。表現に違和感がある場合は、原文(英語)を参照するか、翻訳にご協力ください。
関数と高階関数(Higher-order function)は、関数型プログラミング言語における重要な構成要素です。
Flix では、トップレベルの関数は def キーワードを使って定義します。例えば次のようになります:
def add(x: Int32, y: Int32): Int32 = x + y + 1
定義は、関数名に続いて引数リスト、戻り値の型、そして関数本体から構成されます。Flix は型推論をサポートしていますが、トップレベルの関数定義では引数の型と戻り値の型を宣言する必要があります。
Flix では、すべての関数の引数とローカル変数は使用されなければなりません。もし関数の引数が使用されない場合は、未使用であることを明示するためにアンダースコアを接頭辞として付ける必要があります。
高階関数
高階関数(Higher-order function) とは、それ自体が関数であるパラメータを受け取る関数のことです。例えば次のようになります:
def twice(f: Int32 -> Int32, x: Int32): Int32 = f(f(x))
ここで twice 関数は、関数 f と整数 x という 2 つの引数を受け取り、f を x に 2 回適用します。
twice 関数にはラムダ式(Lambda expression)を渡すことができます:
twice(x -> x + 1, 42)
これは 42 が 2 回インクリメントされるため、44 に評価されます。
また、2 つの引数を受け取る関数を必要とする高階関数を定義することもできます:
def twice(f: (Int32, Int32) -> Int32, x: Int32): Int32 =
f(f(x, x), f(x, x))
これは次のように呼び出すことができます:
twice((x, y) -> x + y, 42)
高階関数は、次のようにトップレベルの関数を渡して呼び出すこともできます:
def inc(x: Int32): Int32 = x + 1
def twice(f: Int32 -> Int32, x: Int32): Int32 = f(f(x))
twice(inc, 42)
関数型の構文
関数の引数の数に応じて、関数型の構文は異なります:
Unit -> Int32 // 引数を取らない関数の場合
Int32 -> Int32 // 1 引数の関数の場合
(Int32, Int32, ...) -> Int32 // それ以外の場合
関数合成
Flix は、関数合成(Function composition)とパイプライン処理のためのいくつかの演算子をサポートしています:
let f = x -> x + 1;
let g = x -> x * 2;
let h = f >> g; // x -> g(f(x)) と同等
ここで >> は前方関数合成(forward function composition)です。
また、パイプライン演算子(Pipeline operator)を使って関数適用を記述することもできます:
List.range(1, 100) |>
List.filter(x -> x `Int32.mod` 2 == 0) |>
List.map(x -> x * x) |>
println;
ここで x |> f は、関数適用 f(x) と同等です。
デフォルトでカリー化
関数はデフォルトでカリー化(curried)されています。カリー化された関数は、宣言している引数の数より少ない引数で呼び出すことができ、残りの引数を受け取る新しい関数を返します。例えば次のようになります:
def sum(x: Int32, y: Int32): Int32 = x + y
def main(): Unit \ IO =
let inc = sum(1);
inc(42) |> println
ここで sum 関数は x と y という 2 つの引数を受け取りますが、main 内では 1 つの引数だけで呼び出されています。この呼び出しは、sum と似た新しい関数を返しますが、この関数では x が常に 1 に束縛されている点が異なります。したがって、inc を 42 で呼び出すと 43 が返されます。
カリー化は、多くのプログラミングパターンで役立ちます。例えば、List.map 関数を考えてみましょう。この関数は、型 a -> b の関数と型 List[a] のリストという 2 つの引数を受け取り、その関数をリストのすべての要素に適用して得られる List[b] を返します。さて、カリー化とパイプライン演算子 |> を組み合わせると、次のように書くことができます:
def main(): Unit \ IO =
List.range(1, 100) |>
List.map(x -> x + 1) |>
println
ここで List.map の呼び出しには関数 x -> x + 1 が渡されており、これはリスト引数を期待する新しい関数を 返します。このリスト引数は、パイプライン演算子 |> によって供給されます。この場合、|> はリストと、リストを受け取る関数を期待しています。
パイプライン
Flix はパイプライン演算子 |> をサポートしています。これは単に関数適用の前置版です(つまり、引数が関数より前に現れます)。
パイプライン演算子は、関数型のコードをより読みやすくするためにしばしば使うことができます。例えば次のようになります:
let l = 1 :: 2 :: 3 :: Nil;
l |>
List.map(x -> x * 2) |>
List.filter(x -> x < 4) |>
List.count(x -> x > 1)
もう一つの例を示します:
"Hello World" |> String.toUpperCase |> println
演算子
Flix には、いくつかの組み込みの単項演算子と中置演算子(Infix operator)があります。さらに Flix では、関数名をバッククォートで囲むことで中置関数適用(infix function application)をサポートしています。例えば次のようになります:
123 `sum` 456
これは、通常の関数呼び出しと同等です:
sum(123, 456)
さらに、演算子名(+、-、*、<、>、=、!、&、|、^、$ の組み合わせ)で名付けられた関数も、中置記法で使うことができます。例えば次のようになります:
def <*>(x: Int32, y: Int32): Int32 = ???
これは次のように使うことができます:
1 <*> 2