関数
関数は、あらゆるプログラミング言語においてプログラムの実行の基本となる単位です。 他の言語の場合と同様に、F# の関数にもそれぞれ名前と本体があり、パラメーターや引数を受け取ることができます。 F# ではさらに、関数型プログラミング構成要素もサポートしています。たとえば、関数を値として処理したり、名前のない関数を式で使用したりできます。また、関数の合成による新しい関数の作成、カリー化関数、関数の引数の部分適用による関数の暗黙の定義などがサポートされます。
関数を定義するには let
キーワードを使用します。再帰関数の場合は、let rec
というキーワードの組み合わせを使用します。
構文
// Non-recursive function definition.
let [inline] function-name parameter-list [ : return-type ] = function-body
// Recursive function definition.
let rec function-name parameter-list = recursive-function-body
Remarks
function-name は、関数を表す識別子です。 parameter-list は、スペースで区切られた一連のパラメーターで構成されます。 「パラメーター」で説明するように、各パラメーターの明示的な型を指定できます。 特定の引数型を指定しない場合は、コンパイラによって型が関数本体から推測されます。 function-body は式で構成されます。 関数本体を構成する式は、通常、複数の式から成る複合式で、その最後の式が戻り値になります。 return-type では、コロンの後に戻り値の型を指定します。これは省略可能です。 戻り値の型を明示的に指定しない場合は、コンパイラによって最後の式から特定されます。
単純な関数定義は、次のようになります。
let f x = x + 1
この例では、関数の名前は f
、引数は x
、引数の型は int
、関数本体は x + 1
、戻り値の型は int
です。
関数は、inline
としてマークできます。 inline
の詳細については、「Inline Functions」(インライン関数) を参照してください。
スコープ
モジュール スコープ以外のスコープの任意のレベルでは、値または関数の名前を再利用してもエラーになりません。 名前を再利用する場合、後から宣言した名前が前に宣言した名前をシャドウします。 ただし、モジュールの最上位のスコープでは名前が一意である必要があります。 たとえば次のコードは、モジュール スコープではエラーになりますが、関数内ではエラーになりません。
let list1 = [ 1; 2; 3]
// Error: duplicate definition.
let list1 = []
let function1 () =
let list1 = [1; 2; 3]
let list1 = []
list1
しかし、次のコードはどのスコープ レベルでも許容されます。
let list1 = [ 1; 2; 3]
let sumPlus x =
// OK: inner list1 hides the outer list1.
let list1 = [1; 5; 10]
x + List.sum list1
パラメーター
パラメーターの名前は、関数名の後に指定されます。 次の例のように、パラメーターの型を指定できます。
let f (x : int) = x + 1
型を指定する場合は、パラメーターの名前の後にコロンで区切って指定します。 パラメーターの型を省略した場合、パラメーターの型がコンパイラによって推論されます。 たとえば、次の関数定義で、1 の型が int
であるため、引数 x
の型は int
と推論されます。
let f x = x + 1
ただし、コンパイラは、可能な限り、関数を汎用的にしようとします。 たとえば次のようなコードがあるとします。
let f x = (x, x)
関数は、任意の型の 1 つの引数からタプルを作成します。 型が指定されていないために、任意の引数の型と関数を使用できます。 詳細については、「自動ジェネリック化」を参照してください。
関数本体
関数本体には、ローカル変数と関数の定義を含めることができます。 それらの変数と関数のスコープは、現在の関数の本体内に限られます。 次の例に示すように、定義が関数本体に含まれていることをインデントによって示す必要があります。
let cylinderVolume radius length =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
詳細については、「コードのフォーマットに関するガイドライン」および「Verbose Syntax」 (冗語構文) を参照してください。
戻り値
コンパイラは、関数本体の最後の式を使用して戻り値とその型を特定します。 最後の式の型が前の式から推論される場合もあります。 前のセクションの関数 cylinderVolume
では、pi
の型は、リテラル 3.14159
の型から float
と特定されます。 コンパイラは、pi
の型を使用して、式 length * pi * radius * radius
の型が float
であると判断します。 このため、関数全体の戻り値の型が float
になります。
戻り値の型を明示的に指定するには、次のようにコードを記述します。
let cylinderVolume radius length : float =
// Define a local value pi.
let pi = 3.14159
length * pi * radius * radius
上記のコードが書き込まれると、コンパイラは関数全体に float を適用します。パラメーターの型にも適用する場合は、次のコードを使用します。
let cylinderVolume (radius : float) (length : float) : float
関数の呼び出し
関数を呼び出すには、関数名の後にスペースを入れ、その後にスペースで区切った引数を指定します。 たとえば、関数 cylinderVolume を呼び出して結果を値 vol に割り当てるには、次のコードを記述します。
let vol = cylinderVolume 2.0 3.0
引数の部分適用
指定されている数より少ない数の引数を渡すと、残りの引数を使用する新しい関数が作成されます。 これは、カリー化と呼ばれる引数の処理方法で、F# のような関数型プログラミング言語の特徴の 1 つです。 たとえば、半径がそれぞれ 2.0 と 3.0 の 2 つのパイプを使用しているとします。 次のようにして、パイプの体積を特定する関数を作成することができます。
let smallPipeRadius = 2.0
let bigPipeRadius = 3.0
// These define functions that take the length as a remaining
// argument:
let smallPipeVolume = cylinderVolume smallPipeRadius
let bigPipeVolume = cylinderVolume bigPipeRadius
その後、必要に応じて、2 つのサイズのパイプのさまざまな長さを最後の引数として指定します。
let length1 = 30.0
let length2 = 40.0
let smallPipeVol1 = smallPipeVolume length1
let smallPipeVol2 = smallPipeVolume length2
let bigPipeVol1 = bigPipeVolume length1
let bigPipeVol2 = bigPipeVolume length2
再帰関数
再帰関数はそれらの関数自体を呼び出す関数です。 再帰関数を使用するには、let キーワードの後に rec キーワードを指定する必要があります。 関数の本体から再帰関数を呼び出す方法は、他の関数呼び出しの場合と変わりません。 次の再帰関数は、n 番目のフィボナッチ数を計算します。 フィボナッチ数列は、古代から知られている数列で、数例の各数値が、前の 2 つの連続する数値の和になります。
let rec fib n = if n < 2 then 1 else fib (n - 1) + fib (n - 2)
再帰関数は、特殊な手法 (末尾再帰、アキュムレータ、継続の使用など) を意識して慎重に使用しないと、プログラム スタックのオーバーフローを引き起こしたり、プログラムの実行効率が低下したりする可能性があります。
関数の値
F# では、すべての関数が値と見なされ、実際に、関数値と呼ばれています。 関数は値であるため、他の関数の引数として使用したり、値が使用されるその他のコンテキストで使用したりできます。 次に関数値を引数として受け取る関数の例を示します。
let apply1 (transform : int -> int ) y = transform y
関数値の型を指定するには、->
トークンを使用します。 このトークンの左側に引数の型を、右側に戻り値の型を指定します。 上の例の apply1
は、関数 transform
を引数として受け取る関数で、transform
は、整数を受け取る、別の整数を返す関数です。 apply1
の使用例を次のコードに示します。
let increment x = x + 1
let result1 = apply1 increment 100
前のコードを実行した後の result
の値は 101 になります。
複数の引数を指定するには、次の例に示すように、連続する ->
トークンで区切ります。
let apply2 ( f: int -> int -> int) x y = f x y
let mul x y = x * y
let result2 = apply2 mul 10 20
結果は 200 になります。
ラムダ式
ラムダ式とは、名前のない関数です。 前の例では、名前のある関数 increment と mul を定義しましたが、次のように、代わりにラムダ式を使用することもできます。
let result3 = apply1 (fun x -> x + 1) 100
let result4 = apply2 (fun x y -> x * y ) 10 20
ラムダ式を定義するには、fun
キーワードを使用します。 ラムダ式は関数定義に似ていますが、引数リストと関数本体の区切りに =
トークンではなく ->
トークンを使用する点が異なります。 通常の関数定義と同様に、引数の型は、推論されるようにすることも、明示的に指定することもできます。ラムダ式の戻り値の型も、本体の最後の式の型から推論されます。 詳細については、「ラムダ式: キーワード」を参照してください。
Pipelines
パイプ演算子 |>
は、F# でデータを処理するときに広く使用されます。 この演算子を使用すると、関数の "パイプライン" を柔軟な方法で確立できます。 パイプライン処理を使用すると、複数の関数呼び出しを一連の操作として連結することができます。
let result = 100 |> function1 |> function2
次の例では、これらの演算子を使用して簡単な機能のパイプラインを構築する方法を示します。
/// Square the odd values of the input and add one, using F# pipe operators.
let squareAndAddOdd values =
values
|> List.filter (fun x -> x % 2 <> 0)
|> List.map (fun x -> x * x + 1)
let numbers = [ 1; 2; 3; 4; 5 ]
let result = squareAndAddOdd numbers
結果は [2; 10; 26]
です。 前のサンプルでは、リスト処理関数を使用して、パイプラインの構築時に関数を使用してデータを処理する方法を示しています。 パイプライン演算子自体は、F# コア ライブラリで次のように定義されています。
let (|>) x f = f x
関数合成
F# の関数は、その他の関数から構成することができます。 function1 と function2 という 2 つの関数を合成すると、function1 に続いて function2 を適用する別の関数になります。
let function1 x = x + 1
let function2 x = x * 2
let h = function1 >> function2
let result5 = h 100
結果は 202 になります。
合成演算子 >>
は 2 つの関数を受け取り、関数を返します。これに対し、パイプライン演算子 |>
は値と関数を受け取り、値を返します。 次のコード例は、関数のシグネチャと使用の違いを示すことによって、パイプラインと合成演算子の違いを示しています。
// Function composition and pipeline operators compared.
let addOne x = x + 1
let timesTwo x = 2 * x
// Composition operator
// ( >> ) : ('T1 -> 'T2) -> ('T2 -> 'T3) -> 'T1 -> 'T3
let Compose2 = addOne >> timesTwo
// Backward composition operator
// ( << ) : ('T2 -> 'T3) -> ('T1 -> 'T2) -> 'T1 -> 'T3
let Compose1 = addOne << timesTwo
// Result is 5
let result1 = Compose1 2
// Result is 6
let result2 = Compose2 2
// Pipelining
// Pipeline operator
// ( |> ) : 'T1 -> ('T1 -> 'U) -> 'U
let Pipeline2 x = addOne x |> timesTwo
// Backward pipeline operator
// ( <| ) : ('T -> 'U) -> 'T -> 'U
let Pipeline1 x = addOne <| timesTwo x
// Result is 5
let result3 = Pipeline1 2
// Result is 6
let result4 = Pipeline2 2
オーバーロードの対象となる関数
関数ではなく型のメソッドをオーバー ロードすることができます。 詳細については、「メソッド」を参照してください。