参照セル (F#)
参照セルは、参照セマンティクスを持つ変更可能な値を作成できる格納場所です。
ref expression
解説
値をカプセル化する新しい参照セルを作成するには、値の前に ref 演算子を指定します。 基になる値は変更可能なので、後で変更できます。
参照セルは、単なるアドレスではなく、実際の値を保持します。 ref 演算子を使用して参照セルを作成すると、基の値のコピーが、カプセル化された変更可能な値として作成されます。
参照セルを逆参照するには、! (感嘆符) 演算子を使用します。
次のコード例は、参照セルの宣言と使用方法を示しています。
// Declare a reference.
let refVar = ref 6
// Change the value referred to by the reference.
refVar := 50
// Dereference by using the ! operator.
printfn "%d" !refVar
出力は 50 になります。
参照セルは、次のように宣言される Ref ジェネリック レコード型のインスタンスです。
type Ref<'a> =
{ mutable contents: 'a }
'a ref 型は、Ref<'a> のシノニムです。 コンパイラと IDE の IntelliSense では、この型について前者が表示されますが、基になる定義は後者です。
ref 演算子は、新しい参照セルを作成します。 次のコードは、ref 演算子の宣言です。
let ref x = { contents = x }
次の表に、参照セルで使用できる機能を示します。
演算子、メンバー、またはフィールド |
説明 |
型 |
定義 |
---|---|---|---|
! (逆参照演算子) |
基になる値を返します。 |
'a ref -> 'a |
let (!) r = r.contents |
:= (代入演算子) |
基になる値を変更します。 |
'a ref -> 'a -> unit |
let (:=) r x = r.contents <- x |
ref (演算子) |
新しい参照セルに値をカプセル化します。 |
'a -> 'a ref |
let ref x = { contents = x } |
Value (プロパティ) |
基になる値を取得または設定します。 |
unit -> 'a |
member x.Value = x.contents |
contents (レコード フィールド) |
基になる値を取得または設定します。 |
'a |
let ref x = { contents = x } |
基になる値にアクセスする方法はいくつかあります。 逆参照演算子 (!) によって返される値は、代入可能な値ではありません。 したがって、基になる値を変更する場合は、代わりに代入演算子 (:=) を使用する必要があります。
Value プロパティと contents フィールドは、いずれも代入可能な値です。 したがって、次のコードに示すように、これらを使用して基になる値にアクセスしたり、基になる値を変更したりできます。
let xRef : int ref = ref 10
printfn "%d" (xRef.Value)
printfn "%d" (xRef.contents)
xRef.Value <- 11
printfn "%d" (xRef.Value)
xRef.contents <- 12
printfn "%d" (xRef.contents)
出力は次のとおりです。
10
10
11
12
contents フィールドは、他のバージョンの ML との互換性のために用意されており、コンパイル中に警告を生成します。 この警告を無効にするには、--mlcompatibility コンパイラ オプションを使用します。 詳細については、「コンパイラ オプション (F#)」を参照してください。
使用例
次のコードは、パラメーターの引き渡しでの参照セルの使用方法を示しています。 Incrementor 型には、パラメーター型に byref を含むパラメーターを受け取る Increment メソッドがあります。 パラメーター型の byref は、呼び出し元が参照セルまたは指定した型 (この場合は int) の典型的な変数のアドレスを渡す必要があることを示します。 残りのコードは、これらの型の引数により Increment を呼び出す方法と、変数で ref 演算子を使用して参照セルを作成する方法 (ref myDelta1) を示しています。 次に、アドレス演算子 (&) を使用して適切な引数を生成する方法を示しています。 最後に、let 束縛を使用して宣言した参照セルを使用することにより、Increment メソッドを再び呼び出しています。 コードの最後の行は、! 演算子を使用して出力用に参照セルを逆参照する方法を示しています。
type Incrementor(delta) =
member this.Increment(i : int byref) =
i <- i + delta
let incrementor = new Incrementor(1)
let mutable myDelta1 = 10
incrementor.Increment(ref myDelta1)
// Prints 10:
printfn "%d" myDelta1
let mutable myDelta2 = 10
incrementor.Increment(&myDelta2)
// Prints 11:
printfn "%d" myDelta2
let refInt = ref 10
incrementor.Increment(refInt)
// Prints 11:
printfn "%d" !refInt
参照渡しの方法の詳細については、「パラメーターと引数 (F#)」を参照してください。
注意
C# のプログラマは、F# での ref の機能が C# と異なることに注意する必要があります。たとえば、引数を渡すときに ref を使用した場合、その効果が F# と C# では異なります。
参照セルと変更可能な変数
参照セルと変更可能な変数は、多くの場合、同じ状況で使用できます。 ただし、状況によっては、変更可能な変数を使用できないため、代わりに参照セルを使用する必要があります。 一般に、コンパイラによって許容される場合は、変更可能な変数を使用する必要があります。 しかし、クロージャを生成する式では、変更可能な変数を使用できないことがコンパイラによって報告されます。 クロージャは、ラムダ式、シーケンス式、コンピュテーション式、部分的に適用された引数を使用するカリー化関数など、一部の F# 式によって生成されるローカル関数です。 これらの式によって生成されたクロージャは、以降の評価のために格納されます。 このプロセスは、変更可能な変数とは互換性がありません。 したがって、そのような式で変更可能な状態が必要な場合は、参照セルを使用する必要があります。 クロージャの詳細については、「Closures (F#) (クロージャ (F#))」を参照してください。
次のコード例は、参照セルを使用する必要があるシナリオを示しています。
// Print all the lines read in from the console.
let PrintLines1() =
let mutable finished = false
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> printfn "line is: %s" s
// Attempt to wrap the printing loop into a
// sequence expression to delay the computation.
let PrintLines2() =
seq {
let mutable finished = false
// Compiler error:
while not finished do
match System.Console.ReadLine() with
| null -> finished <- true
| s -> yield s
}
// You must use a reference cell instead.
let PrintLines3() =
seq {
let finished = ref false
while not !finished do
match System.Console.ReadLine() with
| null -> finished := true
| s -> yield s
}
このコードでは、参照セル finished がローカル状態に含まれています。つまり、クロージャ内の変数は、式 (この場合はシーケンス式) の内部で完全に作成および使用されます。 ここで、変数がローカルでないときの状況を説明します。 クロージャは非ローカル状態にもアクセスできますが、アクセスすると、変数はコピーされ、値で格納されます。 このプロセスを、値セマンティクスといいます。 つまり、コピー時の値が格納され、変数への以降のすべての変更は反映されません。 非ローカル変数の変更を追跡する場合、つまり参照セマンティクスを使用することにより非ローカル状態と対話するクロージャが必要な場合は、参照セルを使用する必要があります。
次のコード例は、クロージャでの参照セルの使用方法を示しています。 この場合、クロージャは、関数引数の部分的な適用に起因します。
// The following code demonstrates the use of reference
// cells to enable partially applied arguments to be changed
// by later code.
let increment1 delta number = number + delta
let mutable myMutableIncrement = 10
// Closures created by partial application and literals.
let incrementBy1 = increment1 1
let incrementBy2 = increment1 2
// Partial application of one argument from a mutable variable.
let incrementMutable = increment1 myMutableIncrement
myMutableIncrement <- 12
// This line prints 110.
printfn "%d" (incrementMutable 100)
let myRefIncrement = ref 10
// Partial application of one argument, dereferenced
// from a reference cell.
let incrementRef = increment1 !myRefIncrement
myRefIncrement := 12
// This line also prints 110.
printfn "%d" (incrementRef 100)
// Reset the value of the reference cell.
myRefIncrement := 10
// New increment function takes a reference cell.
let increment2 delta number = number + !delta
// Partial application of one argument, passing a reference cell
// without dereferencing first.
let incrementRef2 = increment2 myRefIncrement
myRefIncrement := 12
// This line prints 112.
printfn "%d" (incrementRef2 100)