パラメータと引数
Midoliy|F#プログラミング

パラメータと引数の概要
サンプルでみるパラメータ
名前付き引数
オプションパラメータ
パラメータの参照渡し
パラメータ配列

パラメータと引数の概要


 関数にはパラメータと引数という考え方があります。関数宣言時にはパラメータを関数の利用側から受け取ることができます。また、関数の利用側はパラメータを受け取る関数に対して引数を渡すことができます。このパラメータと引数の違いは何でしょうか?
 これは関数の宣言側と利用側での呼び方の違いでしかありません。関数の宣言側からするとまだ実体のない値を受け取ったことにして処理を宣言していきます。このときの "まだ実体のない値" のことを パラメータ(= 仮引数) と呼びます。逆に関数の利用側から関数に渡す値のことを 引数(= 実引数) と呼びます。多くの場合、両方合わせて「引数」と呼びますが、厳密には言葉の定義が分けられていますので、注意しましょう。


サンプルでみるパラメータ


 F#の関数はさまざまな値をパラメータとして受け取ることができます。その受け取り方もタプル形式またはカリー化形式、あるいはその2つの組み合わせから指定することができます。
 以下がそれぞれの受け取り方の違いを示した簡単なサンプルになります。
// タプル形式
member this.SomeMethod (param1, param2) = ...

// カリー化形式
let function1 param1 param2 = ...

// タプル形式とカリー化形式の複合
let function2 param1 (param2a, param2b) param3 = ...
                

 クラスのメンバメソッドは通常、タプル形式でパラメータを指定します。これは .NETメソッド のパラメータがタプル形式として認識されるためです。クラスのメンバメソッドを .NET と一致させておくことで、他の言語との連携時に齟齬なく利用することが可能となります。F#は .NET言語ファミリの一員なので、この辺りも考慮した作りにしておくべきでしょう。
 カリー化形式は、let束縛で作成される (いわゆる普通の) 関数で最もよく使用されます。また、F#ではタプル型をよく使う兼ね合いから、タプル形式とカリー化形式の複合したパラメータ指定も使われます。タプル形式とカリー化形式のどちらが特に優れているということはないのですが、部分適用や高階関数などとの親和性を考えて「メンバメソッドにはタプル形式」、「それ以外はカリー化形式」を使う、としてしまってもよいと思います。

// ----------------------------------------------
// タプル形式のパラメータをもつ関数の例
// ----------------------------------------------
type Person (name, age) = class
let name = name
let age = age
// tool(= 道具) と target(= 処理対象) をタプル形式で受け取り、
// tool を使って処理をするメンバメソッドの宣言.
member this.Use (tool, target) = tool target
// unit型を受け取り, 挨拶をするメンバメソッドの宣言.
member this.Greet () = printfn "Hello!! I'm %s." name
end
[<EntryPoint>]
let main argv =
// --------------------------------------------------------
// 初期化をするときもタプル形式で引数を指定する必要があります.
//
let midoliy = new Person ("midoliy", 29)
// ----------------------------------------------
// メンバメソッドの利用例
//
let tool x = x ** x
// tool と 値 をタプル型で引数に渡して結果を受け取ります.
let result = midoliy.Use (tool, 10.)
printfn "midoliy.Use (tool, 10.)= %f" result
//
// output:
// midoliy.Use (tool, 10.)= 10000000000.000000
//
// .NET の 引数なしメソッド と unit型を引数に取るメソッド は互換性があります.
midoliy.Greet ()
//
// output:
// Hello!! I'm midoliy.
//
0
view raw args1.fs hosted with ❤ by GitHub

 上記のサンプルでは、タプル形式のパラメータを持つメンバメソッドを紹介しています。また、他の.NET言語では引数なしのメソッドを提供していますが、F#では基本的には引数なしの関数・メソッドを提供していません。その代わりに unit型 を引数に持つメソッドを作成することで代用しています。
 ここで重要なことは、.NETクラスのメンバメソッドを利用する際は必ずタプル型で引数が渡されてくるということです(ただし、引数が2つ以上の場合のみ)。そのため、関数の部分適用が利用できないなどのデメリットが発生することは意識しておかなければなりません。

// ----------------------------------------------
// カリー化形式のパラメータをもつ関数の例
// ----------------------------------------------
// 足し算と引き算をする単純な関数
let add x y = x + y
let sub x y = x - y
// 関数を1つとパラメータを2つ受け取る, delegateのような関数
let calc f x y = f x y
// add と sub を組み合わせた処理内容に意味はないサンプル用の関数
let composite x y = add x y |> sub x
[<EntryPoint>]
let main argv =
// 通常, 以下のように引数へ値を渡すことで関数を使用できます.
let result1 = add 10 20
let result2 = sub 10 20
printfn "add 10 20= %d" result1
printfn "sub 10 20= %d" result2
//
// output:
// add 10 20= 30
// sub 10 20= -10
//
// また, カリー化形式にすることで関数の部分適用が可能になります.
let add10 = add 10
let result3 = add10 30
let result4 = add10 21
printfn "add10 30= %d" result3
printfn "add10 21= %d" result4
//
// output:
// add10 30= 40
// add10 21= 31
//
// 引数に関数を渡すことも可能です.
let result5 = calc composite 10 20
printfn "calc composite 10 20= %d" result5
//
// output:
// calc composite 10 20= -20
//
// 当然, 関数も部分適用することができます.
let calcAdd = calc add
let result6 = calcAdd 40 50
printfn "calcAdd 40 50= %d" result6
//
// output:
// calcAdd 40 50= 90
//
0
view raw args2.fs hosted with ❤ by GitHub

 上記のサンプルでは、カリー化形式のパラメータを持つ関数を紹介しています。カリー化については別ページで紹介するので詳細は省略させていただきます。ここでは「カリー化をすると部分適用とかの便利機能が使えるんだなー」くらいに留めておいていただければ大丈夫です。
 F#などの関数型プログラミングでは、カリー化や高階関数などを駆使したコードを書きます。言葉の響きだけだと難しそうですが、使ってみると案外簡単なのであまり身構えずに学習を進めてください。


名前付き引数


 F#のメソッドの引数は、パラメータリスト内の順序と同じ順序で指定することも、パラメータ名の後に = (= 等号)を付けて値を指定することもできます。この等号を利用した引数の指定方法を名前付き引数と呼びます。名前付き引数の場合、パラメータリストの順序とは違う順序で指定することも可能です。
 なぜ、通常の引数指定の方法以外に「名前付き引数」が必要なのでしょうか?それは一重に、可読性保守性のためです。名前付き引数を利用することによって、APIの仕様変更に対してコードを修正しやすくなったり、そもそも関数やメソッドに対して「何を渡せばいいのか」が明確になります。これは、作成した人以外の人がコードを読むときなどに便利です。さらには、コード作成者である自分自身がコードを読みなおすときに、コードを思い出す負担が軽くなります。ただし、コード量が増加するため、あまり引数が多くないメソッドや関数の場合には使わないことが多いです。この辺りに関しては開発現場のコーディングルールに沿うようにしましょう。
 名前付き引数はメンバメソッドにのみ使用できます。let束縛関数・関数値・ラムダ式には使用できないので注意しましょう。

 以下は、前節で利用したPersonクラスの簡易版を利用したサンプルになります。
 
// Useメソッドのみ持っているPersonクラスの宣言
type Person () = class
// tool(= 道具) と target(= 処理対象) をタプル形式で受け取り、
// tool を使って処理をするメンバメソッドの宣言.
member this.Use (tool, target) = tool target
end
[<EntryPoint>]
let main argv =
let person = new Person ()
let f x = x ** 2.
//【通常のメンバメソッドの利用例】
//  引数を (tool, target) の順に並んでいるタプル型として指定します.
let result = person.Use (f, 10.)
// --------------------------------------------------------------
//【名前付き引数の利用例】
//  パラメータ名と値を利用して引数を指定します.
let result = person.Use (tool=f, target=10.)
// 名前付き引数の場合は, タプル内の順序は自由に変更できます.
let result = person.Use (target=10., tool=f)
// 名前付き引数を利用する場合は, すべての引数に対してパラメータ名を指定する必要があります.
// ただし, パラメータリストと順番が同一でかつ,
// 後方のパラメータにのみ名前指定している場合は例外的に許容されています.
let result = person.Use (f, target=10.) //【OK】パラメータリストと順番が同一かつ, 後方のパラメータのみ名前指定
let result = person.Use (tool=f, 10.) //【NG】パラメータリストと順番が同一かつ, 前方のパラメータのみ名前指定
let result = person.Use (10., tool=f) //【NG】パラメータリストと順番が相違かつ, 後方のパラメータのみ名前指定
let result = person.Use (target=10., f) //【NG】パラメータリストと順番が相違かつ, 前方のパラメータのみ名前指定
0
view raw args3.fs hosted with ❤ by GitHub



オプションパラメータ


 F#には指定してもしなくてもよい、省略可能なパラメータ(= オプションパラメータ)を作成することが可能です。オプションパラメータはF#の option型 として解釈されるため、 match式を利用して処理を分岐させることができます。
 名前付き引数と同様、オプションパラメータはメンバメソッドにのみ使用が許可されています。

 オプションパラメータを使用するには、パラメータ名の先頭に "?" を付与することでオプションパラメータとすることが可能です。ただしオプションパラメータの後に通常のパラメータを宣言することはできませんので、注意してください。  オプションパラメータのデフォルト値を指定したい場合、defaultArg関数 を使用します。defaultArg関数は第一引数にオプションパラメータを取り、第二引数にデフォルト値を取ります。オプションパラメータを使う際によく使う関数なので覚えておきましょう。

 以下は、簡易的なサンプルになります。
 
// =========================================================
// オプションパラメータのサンプル
//
// パラメータ名の先頭に ? を付けることでオプションパラメータにすることができます.
type Person (?name0:string, ?age0:int) = class
// ------------------------------------------------
// ● defaultArg関数を利用してデフォルト値を設定
// |> 第一引数が <None> の場合に, 第二引数の値となります.
//
// name0 = None の場合に, "no name" となります.
let name = defaultArg name0 "no name"
// age0 = None の場合に, -1 となります.
let age = defaultArg age0 -1
member this.Name with get() = name
member this.Age with get() = age
end
// 通常のパラメータの後に, オプションパラメータを宣言することもできます.
type Person2 (name0:string, ?age0:int) = class
end
//// オプションパラメータの後に, 通常のパラメータを宣言することはできません.
//// 以下の宣言はエラーとなります.
//type Person3 (?name0:string, age0:int) = class
//end
[<EntryPoint>]
let main argv =
// ------------------------------------------------
// name0 と age0 はオプション引数のため, unit型を指定することでインスタンス化できます.
// その場合は, 初期値としてデフォルト値が設定されます.
//
let p1 = new Person ()
printfn "p1: name= %s, age= %d" p1.Name p1.Age
//
// output:
// p1: name= no name, age= -1
//
// ------------------------------------------------
// 通常のように引数を渡すことでインスタンス化することもできます.
//
let p2 = new Person ("midoliy-1", 29)
// また, 各引数は省略することが可能です.
let p3 = new Person ("midoliy-2")
// 省略する引数が指定したい引数よりもパラメータリストの中で先に宣言されている場合,
// 名前付き引数を利用することで省略することができます.
// 今回の場合, name0 を飛ばして age0 の値を指定したい場合は, 名前付き引数を利用しなければなりません.
let p4 = new Person (age0=19)
// そのため, 以下はエラーとなります.
// let p4 = new Person (19)
printfn "p2: name= %s, age= %d" p2.Name p2.Age
printfn "p3: name= %s, age= %d" p3.Name p3.Age
printfn "p4: name= %s, age= %d" p4.Name p4.Age
//
// output:
// p2: name= midoliy-1, age= 29
// p3: name= midoliy-2, age= -1
// p4: name= no name , age= 19
//
// ------------------------------------------------
// 引数に option型 を指定することもできます. その際には必ず名前付き引数を利用して指定しなければなりません.
//
let p5 = new Person (?name0=Some("midoliy-3"), ?age0=None)
let p6 = new Person (?name0=None)
let p7 = new Person (?age0=Some(20))
printfn "p5: name= %s, age= %d" p5.Name p5.Age
printfn "p6: name= %s, age= %d" p6.Name p6.Age
printfn "p7: name= %s, age= %d" p7.Name p7.Age
//
// output:
// p5: name= midoliy-3, age= -1
// p6: name= no name , age= -1
// p7: name= no name , age= 20
//
0
view raw args4.fs hosted with ❤ by GitHub

 サンプルをみてもらうとわかるとおり、インスタンス化の際にoption型を指定することが可能です。これは、別のメソッドや関数から受け取った option型 をそのまま使用したいときなどに非常に便利です。ただし、option型を指定する際は必ず名前付きで引数を指定しなければならないので注意しましょう。


パラメータの参照渡し


 F#では、パラメータを参照渡しすることができます。参照渡しには3つの指定方法があり、どのタイプを使うかによって挙動が異なります。

inref<'T> 読み取りだけが必要な場合に指定する(= readonly).
引数は初期化されていることが約束されている.
outref<'T> 書き込みだけが必要な場合に指定する(= writeonly).
引数は初期化されていることが約束されていない.
byref<'T> 読み込みも書き込みも必要な場合に指定する(= read-write).
引数は初期化されていることが約束されている.

 値型の受け渡しは通常、値のコピーをすることで行われています。そのため、サイズの大きな値型を関数やメソッドに引数として指定するとパフォーマンスが悪化する恐れがあります。そういった際に参照渡しを利用します。また、参照型の値でも、渡したメソッドや関数の中で変更がされてしまう可能性があります。それらを防ぎたい場合にも参照渡し(特に inref<'T>)を利用します。
 パラメータが参照渡しで指定されている場合、引数の指定をする際に必ず値に & を付与しなければなりません。
 以下は、参照渡しの簡易的な使い方のサンプルです。

// ----------------------------------
// inref<'T> のサンプル
//
// 引数の型を明示的に指定する必要があります.
// 使い方は通常の引数と変わりません.
let inrefExample (x: inref<int>) = printfn "It's %d" x
// もちろん, 値を再設定することはできません.
// 以下はエラーとなります.
//let inrefExample (x: inref<int>) =
// x <- 10
// printfn "It's %d" x
// ----------------------------------
// outref<'T> のサンプル
//
// 引数の型を明示的に指定する必要があります.
let outrefExample (x: outref<int>) =
// outrefは初期化されていない可能性があるため, 以下はエラーになる可能性があります.
// そのため, 通常は x に値を設定するまでは利用しないようにします.
//printfn "%d" x
// mutable値 として利用することができます.
x <- 10
x <- x * x
// ----------------------------------
// byref<'T> のサンプル
//
// 引数の型を明示的に指定する必要があります.
let byrefExample (x: byref<int>) =
// byrefは初期化されていることが約束されているため, xに値を設定する前に利用しても問題ないです.
printfn "byref<int> x= %d" x
// mutable値 として利用することができます.
x <- 15
x <- x * x
[<EntryPoint>]
let main argv =
// ----------------------------------
// inref<'T> の利用例
//
let arg = 10
printfn "before arg= %d" arg
// 引数には & を付与します.
inrefExample &arg
// もちろん, 関数を実行したあとも arg の値は変わっていません.
printfn "after arg= %d" arg
//
// output:
// before arg= 10
// It's 10
// after arg= 10
//
// ----------------------------------
// outref<'T> の利用例
//
let mutable arg = 20
printfn "before arg= %d" arg
// outref<'T> に指定する引数は mutable でないといけません.
outrefExample &arg
// 関数を実行したあとの arg は, 通常, 値に変更が加えられています.
printfn "after arg= %d" arg
//
// output:
// before arg= 20
// after arg= 100
//
// ----------------------------------
// byref<'T> の利用例
//
let mutable arg = 100
printfn "before arg= %d" arg
// byref<'T> に指定する引数は mutable でないといけません.
byrefExample &arg
// 関数を実行したあとの arg は, 値に変更が加えられている可能性があります.
printfn "after arg= %d" arg
//
// output:
// before arg= 100
// byref<int> x= 100
// after arg= 225
//
0
view raw args5.fs hosted with ❤ by GitHub


パラメータ配列


 プログラムを作成していると、printfn関数のように、異なった型の引数を任意の数受け取りたい場合があります。そういった場合に パラメータ配列機能を利用します。
 通常、そういった場合には関数やメソッドのオーバーロード機能を利用して作成しますが、使用可能なすべての型を考慮しつつ、さまざまな組み合わせを作成することは現実的ではありません。そのため、F#ではパラメータ配列という機能が用意されています。これは同じ.NET言語のC#にも同様に用意されている機能になります。
 パラメータ配列を使いたい場合には、[<ParamArray>] 属性を対象の引数に指定します。また、パラメータ配列はパラメータリストの一番最後に宣言しなければなりません。
 以下は簡単な利用例となります。

// パラメータ配列の要素をすべて表示する関数
let printAll ([<ParamArray>] args: Object[]) =
for arg in args do
printfn "%A" arg
// メンバメソッドでの実装も可能
type X () =
member this.printAll ([<ParamArray>] args: Object[]) =
for arg in args do
printfn "%A" arg
[<EntryPoint>]
let main argv =
// -----------------------------------------------------
// 関数の引数として渡す場合は, 配列データとして指定する必要があります.
printAll [| "string"; 10.; 50; 21.f; 98u; |]
//
// output:
// "string"
// 10.0
// 50
// 21.0f
// 98u
//
// -----------------------------------------------------
// メンバメソッドの場合は, タプル形式で渡すことが可能です.
let x = new X ()
x.printAll ("string", 10., 50, 21.f, 98u )
//
// output:
// "string"
// 10.0
// 50
// 21.0f
// 98u
//
0
view raw args6.fs hosted with ❤ by GitHub