自動ジェネリック化 (F#)

F# では、型推論を使用して、関数および式の型を評価します。このトピックでは、F# が、可能な場合に関数の引数および型を自動的に汎化し、それらの関数を複数の型で使用できるようにするしくみについて説明します。

自動ジェネリック化

F# コンパイラは、関数の型推論を実行するときに、特定のパラメーターをジェネリックにできるかどうかを判断します。コンパイラは、各パラメーターを調べ、そのパラメーターの特定の型に関数が依存しているかどうかを判断します。関数が依存していない場合、その型はジェネリックと推論されます。

次のコード例は、コンパイラがジェネリックと推論する関数を示しています。

let max a b = if a > b then a else b

型は、'a -> 'a -> 'a と推論されます。

この型は、これが、同じ未知の型の引数を 2 つ受け取り、同じ型の値を返す関数であることを示しています。前の関数がジェネリックであると考えられる理由の 1 つは、大なり演算子 (>) 自体がジェネリックであることです。大なり演算子のシグネチャは 'a -> 'a -> bool です。すべての演算子がジェネリックというわけではありません。関数内のコードが、ジェネリックでない関数または演算子と共にパラメーター型を使用する場合、そのパラメーター型を汎化することはできません。

max はジェネリックであるため、次の例に示すように、int や float などの型で使用できます。

let biggestFloat = max 2.0 3.0
let biggestInt = max 2 3

ただし、2 つの引数は同じ型である必要があります。シグネチャは 'a -> 'a -> 'a であり、'a -> 'b -> 'a ではありません。したがって、次のコードでは型が一致しないため、エラーが生成されます。

// Error: type mismatch.
let biggestIntFloat = max 2.0 3

max 関数は、大なり演算子をサポートする任意の型でも使用できます。したがって、次のコードに示すように、この関数を文字列でも使用できます。

let testString = max "cab" "cat"

値制限

コンパイラは、関数が、明示的な引数で完全に定義されており、値が単純で変更不可である場合にのみ、自動ジェネリック化を実行します。

つまり、特定の型に十分に制約されておらず、汎化することもできないコードをコンパイルした場合は、コンパイラがエラーを生成します。この問題のエラー メッセージでは、値の自動ジェネリック化についてのこの制約を、値制限と呼びます。

通常、値制限エラーは、構成要素をジェネリックにしたいが、コンパイラに十分な情報がないために汎化できない場合、または非ジェネリックな構成要素で十分な型情報を、誤って省略した場合に発生します。値制限エラーの解決方法は、次のいずれかの方法で、より明示的な情報を提供して、より完全に型推論の問題を制約することです。

  • 値またはパラメーターに明示的な型の注釈を追加することにより、型を非ジェネリックに制約します。

  • 関数合成や不完全に適用されたカリー化関数引数などの、汎化できない構成要素を使用してジェネリック関数を定義していることが問題となっている場合は、関数を通常の関数定義として書き直します。

  • 複雑すぎるために汎化できない式が問題である場合は、追加の未使用のパラメーターを追加して、その式を関数にします。

  • 明示的なジェネリック型パラメーターを追加します。このオプションを使うことはほとんどありません。

  • 次のコード例に、これらの各シナリオを示します。

ケース 1: 式が複雑すぎる場合。この例では、リスト counter が int option ref として作成されていますが、単純な変更不可の値として定義されてはいません。

let counter = ref None
// Adding a type annotation fixes the problem:
let counter : int option ref = ref None

ケース 2: 汎化できない構成要素を使用してジェネリック関数を定義している場合。この例では、関数引数が部分的に適用されるため、構成要素を汎化できません。

let maxhash = max << hash
// The following is acceptable because the argument for maxhash is explicit:
let maxhash obj = (max << hash) obj

ケース 3: 追加の未使用のパラメーターを追加する場合。この式は、複雑すぎて汎化できないため、コンパイラで値制限エラーが発生します。

let emptyList10 = Array.create 10 []
// Adding an extra (unused) parameter makes it a function, which is generalizable.
let emptyList10 () = Array.create 10 []

ケース 4: 型パラメーターを追加する場合。

let arrayOf10Lists = Array.create 10 []
// Adding a type parameter and type annotation lets you write a generic value.
let arrayOf10Lists<'T> = Array.create 10 ([]:'T list)

最後の場合値はたとえば多くの異なる型の値を作成するために使用できる次のように関数の型です :

let intLists = arrayOf10Lists<int>
let floatLists = arrayOf10Lists<float>

参照

関連項目

型推論 (F#)

ジェネリック (F#)

静的に解決された型パラメーター (F#)

制約 (F#)