ラムダ式(匿名関数/無名関数)
F#では
ラムダ式(lambda expression)または
関数式(function expression)と呼ばれる
関数を表現するための式があり、
匿名関数または
無名関数(anonymous function)とも呼ばれます。
まずは、ラムダ式の構文をみてみましょう(f#4.1 spec 6.3.7)(より正確な構文はページ下部)
- function expression
- fun pat1 ... patn -> expr
funがラムダ式が始まることを示すキーワードで
exprは様々な式のこと、つまり関数の本体です。
pat1 ... patnは関数に渡すパラメータのことで
後に説明する様々なパターンが使えます。
このパラメータは
引数(ひきすう)と呼ばれることもあります。
C言語などの関数とは異なり、
ラムダ式は常に引数を持つため
引数は最低でも1つは必要です。
パターンには様々なものがあるので
個別のパターンが出てきた時に別途説明することにして
このページでは、基本的な使い方を説明します。
1を足す関数
> fun x -> x + 1;; //注意。+と1のスペースがないと別の意味
val it : int -> int = <fun:clo@20-12>
これは、入力されたものに1を足す関数です。
引数はx一つだけで、x+1が関数の本体部分です。
また、結果の型について、
関数というのは、「入力」と「出力」との対応関係であるので
関数の入力値に対応した型 -> 関数の出力値に対応した型という風に表記され、
これ全体が関数の型を表します。
つまり、int -> intという型は
「int型の数値を入力として受け取り、int型の数値を返す」関数を表す型ということになります。
コンパイルされた際は入力値に対応した型をty1,出力値に対応した型をty2とすると
Microsoft.FSharp.Core.FastFunc<ty1,ty2>という型になるそうです(f#4.1 spec 5.1.1)
次は関数に値を適用させてみます。
関数を適用するための構文は次のようになります(6.4.1)
- 関数適用の構文
- expr expr | expr(expr)
この通り、単に式を並べるだけになっています。
右側を括弧でくくった場合は、右側を先に計算するという意味になります
関数の適用
> (fun x -> x + 1) 10;; //OK
val it : int = 11
> fun x -> x + 1 10;; //fun x -> x + (1 10)と解釈された
fun x -> x + 1 10;;
-------------^
stdin(22,14): error FS0003: This value is not a function and cannot be applied
気をつけるべき点は、式全体を括弧で囲む必要がある点です。
下の例では、変なところで
関数適用しようとしているラムダ式と見なされてしまい
エラーが起きてしまっています。
引数に関しては、
funの側にあるxと
実際に与える10の2種類がありますが
前者を
仮引数、後者を
実引数と呼び分ける場合もあります。
また、関数に値を適用した結果の値のことを
返り値または
戻り値(return value)と呼びます。
C言語などにあるようなreturn文(明示的に返り値を指定する構文)というものはなく
関数本体部分の一番最後の式の値が返り値になります。
色々な例
引数の名前は、関数自体の意味には影響を与えません。
引数の名前
> fun y -> y + 1;; //上で説明したものと全く同じ関数
val it : int -> int = <fun:clo@23-13>
> (fun y -> y + 1) 10;;
val it : int = 11
> fun longname -> longname + 1;; //これも同じ関数。長い名前でもOK
val it : int -> int = <fun:clo@24-14>
> (fun longname -> longname + 1) 10;;
val it : int = 11
関数は入力と出力の対応関係であるので
このように引数の名前は何にしても問題ありません。
引数が2つの関数
> fun x y -> x + y;;
val it : int -> int -> int = <fun:clo@27-15>
> ((fun x y -> x + y) 10) 20;;
val it : int = 30
> (fun x y -> x + y) 10 20;;
val it : int = 30
今度は引数が2つの関数です。
結果の型を見るとint -> int -> intとなっています。
これは
1つめの入力(int型) -> 2つめの入力(int型) -> 出力(int型)
のように見ることが出来ます。
関数を適用する際は、2番目の例のようにせずとも
3番目のように単純に並べるだけでOKです。
3つ以上の場合も同様です。
型変数
> fun x -> x;;
val it : 'a -> 'a = <fun:clo@30-16>
今度は、入力されたものをそのまま返す関数なのですが
結果の型に、見慣れない'aというものが出てきました。
これは
型変数(type variable)といい、
様々な型を抽象的に表したものです。
関数を呼び出す段階になると
入力がint型の値ならば'aはintに置き換わり
入力がstring型の値ならば'aはstring型に置き換わります。
これまでの例で型変数が出てこなかったのは、
+ 1などと書いてある部分から
整数(int型)を対象にした関数だとF#が自動的に判断したためです。
(1はint型であり、+は片方がint型であれば、もう片方もint型だと判断出来るため)
実際に値を適用してみると
多相的な関数
> (fun x -> x) 10;;
val it : int = 10
> (fun x -> x) "hello";;
val it : string = "hello"
> (fun x -> x) true;;
val it : bool = true
このように、入力に応じた結果が帰ってきます。
'aのような型変数を持つ関数の事を
多相的(polymorphic)な関数と呼ぶこともあります。
上のケースでは入力にも出力にも同じ'aが使われているので
「入力と出力の型は常に同じ」という事を意味します。
複数の型変数が現れる場合は
必要に応じて'b、'c、...といった別の名前が使われます。
型変数を持つ場合でも、
適切に型注釈を付ければ、型変数を持たない定義にすることが出来ます。
(型注釈は、このように個別の引数ごとのパターンにも使えます)
型注釈
> (fun (a:int) -> a);;
val it : int -> int = <fun:clo@66-20>
今度は、引数に関数を取るようなものを作ってみます。
(これには制限があるのですが、letでの関数定義で説明します)
引数に関数を取る関数
> (fun x -> x 10);;
val it : (int -> 'a) -> 'a = <fun:clo@37-18>
> (fun x -> x 10) (fun y -> y + 1);;
val it : int = 11
> (fun x -> x 10) (fun x -> x + 1);;
val it : int = 11
> (fun (x:int -> int) -> x 10);;
val it : (int -> int) -> int = <fun:clo@40-19>
この関数の型は
(int -> 'a) -> 'aという風に括弧がついています。
わかりやすくするため、
注釈を付けて全てint型にしたのが最後の例です。
(int -> int) -> int
というのは
「(int -> int型の関数を入力)として(int型の値を返す)」関数を表す型
という意味になります。
上のほうで出てきたint -> int -> intは引数が2つだったのに対し
今回は括弧がついているので引数は1つだけです。
また、二番目と三番目の例からもわかるように
左側のfunと右側のfunの引数の名前の間には
特に関連性はないので、同じにしても問題ありません。
funの引数は、その本体の中でのみ参照出来るスコープを持ちます。
ただし、fun a -> (fun b -> body)のように入れ子になっている場合は
bのbody部で外側のaを参照することが出来ます。
引数の無い関数のエミュレート
C言語などの多くの言語では、引数の無い関数も定義出来ますが
F#ではそういうものは定義できません。
しかし、慣例的にunit型の値である()を使うことで
同様のものを表現します。
引数の無い関数のエミュレート
> fun () -> 10;;
val it : unit -> int = <fun:clo@34-17>
> (fun () -> 10) ();;
val it : int = 10
この関数は()の入力に対して常に10を返す関数です。
型の見方
F#では、関数が2つ以上の引数を持っていても
全ての引数を使わずに一部だけ適用することも出来ます。
その場合
このような型は、
A'
int -> (int -> (int -> int))
このような型であるとみなすことが出来ます。
(->を演算子とみなすと、右結合のような動作)
(右結合という用語については、演算子の優先順位のページを
また、一部の引数の適用については
関数型のカリー化の章も参考にしてみてください)
AとA'の例の2つの型が同じ型かどうかは
情報が見つけられないのでちょっとわからないのですが
単純に引数をfunで置き換えただけでない場合は
表示上は別の種類の型として表示されるようです。
意図的な例ですが
括弧付きの型の表示
> let f x y = x + y + 1;;
val f : int -> int -> int
> let g x = let a = 1 in fun y -> x + y + a;;
val g : int -> (int -> int)
実際の所、fとgは、使う側から見れば同じように使えます。
function expressionの正確な構文
最後にラムダ式の正確な構文(f#4.1 spec 6,7)の抜粋を載せます。
- function expression
- fun argument-pats -> expr
argument-pats:= atomic-pat ... atomic-pat
atomic-pat := pat
ただし、patは
const,long-ident,list-pat,record-pat,array-pat,(pat),
:? atomic-type,null,_の形であるものに限る