パラメータと引数の概要
関数にはパラメータと引数という考え方があります。関数宣言時にはパラメータを関数の利用側から受け取ることができます。また、関数の利用側はパラメータを受け取る関数に対して引数を渡すことができます。このパラメータと引数の違いは何でしょうか?
これは関数の宣言側と利用側での呼び方の違いでしかありません。関数の宣言側からするとまだ実体のない値を受け取ったことにして処理を宣言していきます。このときの "まだ実体のない値" のことを パラメータ(= 仮引数) と呼びます。逆に関数の利用側から関数に渡す値のことを 引数(= 実引数) と呼びます。多くの場合、両方合わせて「引数」と呼びますが、厳密には言葉の定義が分けられていますので、注意しましょう。
サンプルでみるパラメータ
F#の関数はさまざまな値をパラメータとして受け取ることができます。その受け取り方もタプル形式またはカリー化形式、あるいはその2つの組み合わせから指定することができます。
以下がそれぞれの受け取り方の違いを示した簡単なサンプルになります。
// タプル形式
member this.SomeMethod (param1, param2) = ...
// カリー化形式
let function1 param1 param2 = ...
// タプル形式とカリー化形式の複合
let function2 param1 (param2a, param2b) param3 = ...
クラスのメンバメソッドは通常、タプル形式でパラメータを指定します。これは .NETメソッド のパラメータがタプル形式として認識されるためです。クラスのメンバメソッドを .NET と一致させておくことで、他の言語との連携時に齟齬なく利用することが可能となります。F#は .NET言語ファミリの一員なので、この辺りも考慮した作りにしておくべきでしょう。
カリー化形式は、let束縛で作成される (いわゆる普通の) 関数で最もよく使用されます。また、F#ではタプル型をよく使う兼ね合いから、タプル形式とカリー化形式の複合したパラメータ指定も使われます。タプル形式とカリー化形式のどちらが特に優れているということはないのですが、部分適用や高階関数などとの親和性を考えて「メンバメソッドにはタプル形式」、「それ以外はカリー化形式」を使う、としてしまってもよいと思います。
上記のサンプルでは、タプル形式のパラメータを持つメンバメソッドを紹介しています。また、他の.NET言語では引数なしのメソッドを提供していますが、F#では基本的には引数なしの関数・メソッドを提供していません。その代わりに unit型 を引数に持つメソッドを作成することで代用しています。
ここで重要なことは、.NETクラスのメンバメソッドを利用する際は必ずタプル型で引数が渡されてくるということです(ただし、引数が2つ以上の場合のみ)。そのため、関数の部分適用が利用できないなどのデメリットが発生することは意識しておかなければなりません。
上記のサンプルでは、カリー化形式のパラメータを持つ関数を紹介しています。カリー化については別ページで紹介するので詳細は省略させていただきます。ここでは「カリー化をすると部分適用とかの便利機能が使えるんだなー」くらいに留めておいていただければ大丈夫です。
F#などの関数型プログラミングでは、カリー化や高階関数などを駆使したコードを書きます。言葉の響きだけだと難しそうですが、使ってみると案外簡単なのであまり身構えずに学習を進めてください。
名前付き引数
F#のメソッドの引数は、パラメータリスト内の順序と同じ順序で指定することも、パラメータ名の後に = (= 等号)を付けて値を指定することもできます。この等号を利用した引数の指定方法を名前付き引数と呼びます。名前付き引数の場合、パラメータリストの順序とは違う順序で指定することも可能です。
なぜ、通常の引数指定の方法以外に「名前付き引数」が必要なのでしょうか?それは一重に、可読性と保守性のためです。名前付き引数を利用することによって、APIの仕様変更に対してコードを修正しやすくなったり、そもそも関数やメソッドに対して「何を渡せばいいのか」が明確になります。これは、作成した人以外の人がコードを読むときなどに便利です。さらには、コード作成者である自分自身がコードを読みなおすときに、コードを思い出す負担が軽くなります。ただし、コード量が増加するため、あまり引数が多くないメソッドや関数の場合には使わないことが多いです。この辺りに関しては開発現場のコーディングルールに沿うようにしましょう。
名前付き引数はメンバメソッドにのみ使用できます。let束縛関数・関数値・ラムダ式には使用できないので注意しましょう。
以下は、前節で利用したPersonクラスの簡易版を利用したサンプルになります。
オプションパラメータ
F#には指定してもしなくてもよい、省略可能なパラメータ(= オプションパラメータ)を作成することが可能です。オプションパラメータはF#の option型 として解釈されるため、 match式を利用して処理を分岐させることができます。
名前付き引数と同様、オプションパラメータはメンバメソッドにのみ使用が許可されています。
オプションパラメータを使用するには、パラメータ名の先頭に "?" を付与することでオプションパラメータとすることが可能です。ただしオプションパラメータの後に通常のパラメータを宣言することはできませんので、注意してください。 オプションパラメータのデフォルト値を指定したい場合、defaultArg関数 を使用します。defaultArg関数は第一引数にオプションパラメータを取り、第二引数にデフォルト値を取ります。オプションパラメータを使う際によく使う関数なので覚えておきましょう。
以下は、簡易的なサンプルになります。
サンプルをみてもらうとわかるとおり、インスタンス化の際にoption型を指定することが可能です。これは、別のメソッドや関数から受け取った option型 をそのまま使用したいときなどに非常に便利です。ただし、option型を指定する際は必ず名前付きで引数を指定しなければならないので注意しましょう。
パラメータの参照渡し
F#では、パラメータを参照渡しすることができます。参照渡しには3つの指定方法があり、どのタイプを使うかによって挙動が異なります。
inref<'T> | 読み取りだけが必要な場合に指定する(= readonly). 引数は初期化されていることが約束されている. |
outref<'T> | 書き込みだけが必要な場合に指定する(= writeonly). 引数は初期化されていることが約束されていない. |
byref<'T> | 読み込みも書き込みも必要な場合に指定する(= read-write). 引数は初期化されていることが約束されている. |
値型の受け渡しは通常、値のコピーをすることで行われています。そのため、サイズの大きな値型を関数やメソッドに引数として指定するとパフォーマンスが悪化する恐れがあります。そういった際に参照渡しを利用します。また、参照型の値でも、渡したメソッドや関数の中で変更がされてしまう可能性があります。それらを防ぎたい場合にも参照渡し(特に inref<'T>)を利用します。
パラメータが参照渡しで指定されている場合、引数の指定をする際に必ず値に & を付与しなければなりません。
以下は、参照渡しの簡易的な使い方のサンプルです。
パラメータ配列
プログラムを作成していると、printfn関数のように、異なった型の引数を任意の数受け取りたい場合があります。そういった場合に パラメータ配列機能を利用します。
通常、そういった場合には関数やメソッドのオーバーロード機能を利用して作成しますが、使用可能なすべての型を考慮しつつ、さまざまな組み合わせを作成することは現実的ではありません。そのため、F#ではパラメータ配列という機能が用意されています。これは同じ.NET言語のC#にも同様に用意されている機能になります。
パラメータ配列を使いたい場合には、[<ParamArray>] 属性を対象の引数に指定します。また、パラメータ配列はパラメータリストの一番最後に宣言しなければなりません。
以下は簡単な利用例となります。