パラメーターと引数

このトピックでは、パラメーターを定義して、関数、メソッド、およびプロパティに引数を渡すための言語サポートについて説明します。 これには、参照渡しの方法に関する情報と、可変個の引数を受け取れるメソッドを定義して使用する方法についての情報が含まれます。

パラメーターと引数

"パラメーター" という用語は、指定されることが想定される値の名前を説明するために使用されます。 "引数" という用語は、各パラメーターに指定される値に使用されます。

パラメーターは、タプルまたはカリー化形式、またはその 2 つの組み合わせで指定できます。 引数は、明示的なパラメーター名を使用して渡すことができます。 メソッドのパラメーターを省略可能として指定して、既定値を付与することができます。

パラメーターのパターン

関数およびメソッドに渡すパラメーターは、通常はスペースで区切られたパターンです。 つまり、原則として、関数またはメンバーのパラメーター リストでは、「match 式」で説明されているパターンのいずれかを使用できます。

通常、メソッドでは引数を渡すためにタプル形式が使用されます。 これにより、.NET メソッドで引数が渡される方法とタプル形式が一致するため、他の .NET 言語の観点から結果がより明確になります。

カリー化形式は、let バインディングを使用して作成された関数で最もよく使用されます。

次の擬似コードは、タプルとカリー化引数の例を示しています。

// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...

一部の引数がタプルにあり、一部がない場合は、結合された形式を使用できます。

let function2 param1 (param2a, param2b) param3 = ...

パラメーター リストでは他のパターンを使用することもできますが、パラメーター パターンがすべての可能な入力と一致しない場合は、実行時に不完全な一致が発生する可能性があります。 例外 MatchFailureException は、引数の値がパラメーター リストで指定されたパターンと一致しない場合に生成されます。 パラメーター パターンで不完全な一致が許容されている場合は、コンパイラによって警告が発行されます。 通常、パラメーター リストに役立つパターンが少なくとも他に 1 つあります。それはワイルドカード パターンです。 指定された引数を単に無視する場合は、パラメーター リスト内でワイルドカード パターンを使用します。 次のコードは、引数リスト内でのワイルドカード パターンの使用を示しています。

let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200

ワイルドカード パターンは、次のコードのように、通常は文字列配列として指定されるコマンド ライン引数に関心がなく、プログラムへのメイン エントリ ポイントなどで渡される引数が不要な場合に便利です。

[<EntryPoint>]
let main _ =
    printfn "Entry point!"
    0

引数内で使用されることのあるその他のパターンとしては、as パターン、および判別共用体とアクティブ パターンに関連付けられた識別子パターンがあります。 次のように、単一ケースの判別共用体パターンを使用できます。

type Slice = Slice of int * int * string

let GetSubstring1 (Slice(p0, p1, text)) =
    printfn "Data begins at %d and ends at %d in string %s" p0 p1 text
    text[p0..p1]

let substring = GetSubstring1 (Slice(0, 4, "Et tu, Brute?"))
printfn "Substring: %s" substring

出力は次のとおりです。

Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu

アクティブ パターンは、次の例のように、引数を目的の形式に変換するときなどに、パラメーターとして役立ちます。

type Point = { x : float; y : float }

let (| Polar |) { x = x; y = y} =
    ( sqrt (x*x + y*y), System.Math.Atan (y/ x) )

let radius (Polar(r, _)) = r
let angle (Polar(_, theta)) = theta

次のコード行に示すように、as パターンを使用して、一致した値をローカル値として格納することができます。

let GetSubstring2 (Slice(p0, p1, text) as s) = s

場合により使用されるもう 1 つのパターンは、暗黙的な引数に対してパターン マッチを直ちに実行するラムダ式を関数の本体として指定することにより、最後の引数を無名のままにする関数です。 この例を次にコード行で示します。

let isNil = function [] -> true | _::_ -> false

このコードによって、ジェネリック リストを受け取って、リストが空の場合には true を、それ以外の場合には false を返す関数が定義されます。 このような手法を使用すると、コードが読みにくくなるおそれがあります。

場合によっては、不完全な一致を含むパターンが役に立つことがあります。たとえば、プログラム内のリストに要素が 3 つしかないことがわかっている場合は、パラメーター リストで次のようなパターンを使用できます。

let sum [a; b; c;] = a + b + c

不完全な一致があるパターンの使用は、クイック プロトタイピングやその他の一時的な用途に最適です。 コンパイラにより、このようなコードに対して警告が発行されます。 このようなパターンは、可能なすべての入力の一般的なケースに対応できないため、コンポーネント API には適していません。

名前付き引数

メソッドの引数は、コンマで区切られた引数リスト内の位置によって指定できます。または、名前、等号、渡す値の順に指定してメソッドに明示的に渡すこともできます。 名前を使用して指定した場合は、宣言で使用されているものとは異なる順序で出現することがあります。

名前付き引数を使用すると、コードが読みやすくなり、メソッド パラメーターの並べ替えなど、API の特定の種類の変更への適応性が高まります。

名前付き引数は、let でバインドされた関数、関数値、またはラムダ式ではなく、メソッドに対してのみ使用できます。

名前付き引数の使用方法を次のコード例に示します。

type SpeedingTicket() =
    member this.GetMPHOver(speed: int, limit: int) = speed - limit

let CalculateFine (ticket : SpeedingTicket) =
    let delta = ticket.GetMPHOver(limit = 55, speed = 70)
    if delta < 20 then 50.0 else 100.0

let ticket1 : SpeedingTicket = SpeedingTicket()
printfn "%f" (CalculateFine ticket1)

クラス コンストラクターの呼び出しでは、名前付き引数と同様の構文を使用して、クラスのプロパティ値を設定できます。 次に、この構文の例を示します。

 type Account() =
    let mutable balance = 0.0
    let mutable number = 0
    let mutable firstName = ""
    let mutable lastName = ""
    member this.AccountNumber
       with get() = number
       and set(value) = number <- value
    member this.FirstName
       with get() = firstName
       and set(value) = firstName <- value
    member this.LastName
       with get() = lastName
       and set(value) = lastName <- value
    member this.Balance
       with get() = balance
       and set(value) = balance <- value
    member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
    member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount


let account1 = new Account(AccountNumber=8782108,
                           FirstName="Darren", LastName="Parker",
                           Balance=1543.33)

詳細については、コンストラクター (F#) に関する記事を参照してください。

プロパティ セッターを呼び出すのと同じ手法が、オブジェクトを返すメソッド (ファクトリ メソッドなど) にも適用されます。

type Widget() =
    member val Width = 1 with get,set
    member val Height = 1 with get,set

type WidgetFactory =
    static member MakeNewWidget() =
         new Widget()
    static member AdjustWidget(w: Widget) =
         w
let w = WidgetFactory.MakeNewWidget(Width=10)
w.Width // = 10
w.Height // = 1
WidgetFactory.AdjustWidget(w, Height=10)
w.Height // = 10

それらのメンバーはどのような処理でも実行でき、構文は実質的に、最終的な値を返す前にプロパティ セッターを呼び出すための短縮形であることに注意してください。

省略可能のパラメーター

パラメーター名の前に疑問符を使用して、メソッドの省略可能なパラメーターを指定できます。 省略可能なパラメーターは F# のオプション型として解釈されるので、それらに対するクエリは、Some および Nonematch 式を使用して、オプション型のクエリを実行する通常の方法で実行できます。 省略可能なパラメーターは、let バインディングを使用して作成された関数ではなく、メンバーに対してのみ許可されます。

?arg=None?arg=Some(3)?arg=arg などのパラメーター名を使用して、既存の省略可能な値をメソッドに渡すことができます。 これは、省略可能な引数を別のメソッドに渡すメソッドを作成する場合に役立つ可能性があります。

省略可能な引数の既定値を設定する defaultArg 関数を使用することもできます。 defaultArg 関数では、省略可能なパラメーターを最初の引数として、既定値を 2 番目の引数として受け取ります。

省略可能なパラメーターの使用例を次に示します。

type DuplexType =
    | Full
    | Half

type Connection(?rate0 : int, ?duplex0 : DuplexType, ?parity0 : bool) =
    let duplex = defaultArg duplex0 Full
    let parity = defaultArg parity0 false
    let mutable rate = match rate0 with
                        | Some rate1 -> rate1
                        | None -> match duplex with
                                  | Full -> 9600
                                  | Half -> 4800
    do printfn "Baud Rate: %d Duplex: %A Parity: %b" rate duplex parity

let conn1 = Connection(duplex0 = Full)
let conn2 = Connection(duplex0 = Half)
let conn3 = Connection(300, Half, true)
let conn4 = Connection(?duplex0 = None)
let conn5 = Connection(?duplex0 = Some(Full))

let optionalDuplexValue : option<DuplexType> = Some(Half)
let conn6 = Connection(?duplex0 = optionalDuplexValue)

出力は次のとおりです。

Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false
Baud Rate: 300 Duplex: Half Parity: true
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false

C# と Visual Basic の相互運用を目的として、F# の [<Optional; DefaultParameterValue<(...)>] 属性を使用して、引数が呼び出し元によって省略可能として認識されるようにすることができます。 これは、MyMethod(int i = 3) のように、C# で引数を省略可能として定義することと同じです。

open System
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
        printfn $"{message}"

また、新しいオブジェクトを既定のパラメーター値として指定することもできます。 たとえば、Foo メンバーは代わりに省略可能な CancellationToken を入力として使用できます。

open System.Threading
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
        printfn $"{ct}"

DefaultParameterValue の引数として指定された値は、パラメーターの型と一致する必要があります。 たとえば、以下は許可されません。

type C =
    static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()

この場合、コンパイラによって警告が生成され、両方の属性が完全に無視されます。 既定値 null に型指定の注釈を付ける必要があることに注意してください。そうしないと、コンパイラによって正しくない型 ([<Optional; DefaultParameterValue(null:obj)>] o:obj) が推論されます。

参照渡し

参照による F# 値の受け渡しには、マネージド ポインター型である byref が関係します。 使用する型に関するガイダンスは次のとおりです。

  • ポインターの読み取りのみが必要な場合は、inref<'T> を使用します。
  • ポインターへの書き込みのみが必要な場合は、outref<'T> を使用します。
  • ポインターの読み取りと書き込みの両方を行う必要がある場合は、byref<'T> を使用します。
let example1 (x: inref<int>) = printfn $"It's %d{x}"

let example2 (x: outref<int>) = x <- x + 1

let example3 (x: byref<int>) =
    printfn $"It's %d{x}"
    x <- x + 1

let test () =
    // No need to make it mutable, since it's read-only
    let x = 1
    example1 &x

    // Needs to be mutable, since we write to it
    let mutable y = 2
    example2 &y
    example3 &y // Now 'y' is 3

パラメーターはポインターであり、値は変更可能であるため、値に対する変更は、関数の実行後も保持されます。

戻り値としてタプルを使用して、.NET ライブラリ メソッドに任意の out パラメーターを格納できます。 または、out パラメーターを byref パラメーターとして扱うこともできます。 両方の方法を次のコード例に示します。

// TryParse has a second parameter that is an out parameter
// of type System.DateTime.
let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")

printfn "%b %A" b dt

// The same call, using an address of operator.
let mutable dt2 = System.DateTime.Now
let b2 = System.DateTime.TryParse("12-20-04 12:21:00", &dt2)

printfn "%b %A" b2 dt2

パラメーター配列

場合によっては、任意の数の異種型のパラメーターを受け取る関数を定義する必要があります。 可能なすべてのオーバーロードされたメソッドを作成して、使用可能なすべての型を考慮することは実際的ではありません。 .NET 実装では、パラメーター配列機能を使用して、このようなメソッドがサポートされています。 シグネチャ内でパラメーター配列を受け取るメソッドには、任意の個数のパラメーターを指定できます。 パラメーターは配列に格納されます。 配列要素の型によって、関数に渡すことができるパラメーターの型が決まります。 要素の型として System.Object を使用してパラメーター配列を定義すると、クライアント コードで任意の型の値を渡すことができます。

F# では、パラメーター配列はメソッド内でのみ定義できます。 これらは、スタンドアロン関数、またはモジュール内で定義されている関数では使用できません。

パラメーター配列は、ParamArray 属性を使用して定義します。 ParamArray 属性は、最後のパラメーターにのみ適用できます。

次のコードは、パラメーター配列を受け取る .NET メソッドと、パラメーター配列を受け取るメソッドを持つ F# の型の定義の両方を呼び出す方法を示しています。

open System

type X() =
    member this.F([<ParamArray>] args: Object[]) =
        for arg in args do
            printfn "%A" arg

[<EntryPoint>]
let main _ =
    // call a .NET method that takes a parameter array, passing values of various types
    Console.WriteLine("a {0} {1} {2} {3} {4}", 1, 10.0, "Hello world", 1u, true)

    let xobj = new X()
    // call an F# method that takes a parameter array, passing values of various types
    xobj.F("a", 1, 10.0, "Hello world", 1u, true)
    0

プロジェクト内で実行すると、前のコードの出力は次のようになります。

a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true

関連項目