8

次のコード スニペットでは

type myrec1 = {x: int; y: int}
type myrec2 = {x: int; y: int; z: int}

let p1 = {x = 1; y = 1}  // Error. p1 compiler assumes p1 has the type myrec2

// It works with additional type specification
let p1: myrec1 = {x = 1; y = 1}
let p2: myrec2 = {x = 1; y = 1; z = 1}

コメントのある行はコンパイルされません。何らかの理由で、タイプ チェッカーは、p1 のタイプが myrec1 である必要があることを判断できません。この型推論のケースは単純に解決できないためでしょうか、それとも F# 型推論の単なる制限なのでしょうか?

4

4 に答える 4

9

ここから:

最後に宣言された型のラベルは、以前に宣言された型のラベルよりも優先されます。

したがって、次のようにすると:

type myrec2 = {x: int; y: int; z: int}
type myrec1 = {x: int; y: int}

let p1 = {x = 1; y = 1}

その後、それは動作します。

ここ(F# 3.0 仕様)からお読みください:

field-initializer : long-ident = expr

6.3.5 レコード式

この場合、フィールド初期化子は単一の識別子ではないため、14.1.9 の「フィールド ラベルの解決」を使用します。

各 field-initializeri の形式は field-labeli = expri です。各 field-labeli は long-ident であり、次のように一意のレコード タイプ R のフィールド Fi に解決する必要があります。

· field-labeli が単一の識別子 fld であり、初期型がfld という名前のフィールド Fi を持つレコード型 R< ,.​​.., > であることがわかっている場合、フィールド ラベルは Fi に解決されます。

· field-labeli が単一の識別子でない場合、または初期型が変数型の場合、field-labeli に対してフィールド ラベル解決 (§14.1 を参照) を実行することによって、フィールド ラベルが解決されます。この手順により、一連のフィールド FSeti が生成されます。このセットの各要素には対応するレコード タイプがあり、レコード タイプ RSeti のセットになります。すべての RSeti の共通部分は単一のレコード タイプ R を生成する必要があり、各フィールドは R の対応するフィールドに解決されます。

14.1.9 フィールド ラベルの解決

long-ident は FieldLabel であるため、8.4.2 で説明されている FieldLabels テーブルを使用して検索されます。

フィールド ラベルの解決は、{ field1 = expr; の field1 などの識別子を解決する方法を指定します。... fieldN = expr }. フィールド ラベルの解決では、次の手順を実行します。

1. Types テーブルと FieldLabels テーブル (§8.4.2) で、使用可能なすべてのタイプのすべてのフィールドを検索します。

2. フィールド宣言のセットを返します。

8.4.2 名前解決とレコード フィールド ラベル

ここで説明したように、FieldLabels テーブルはメンバーの名前解決 (14.1) で使用されます。

レコード タイプの場合、レコード タイプに RequireQualifiedAccess 属性がない限り、レコード フィールド ラベル field1 ... fieldN が現在の名前解決環境の FieldLabels テーブルに追加されます。FieldLabels テーブルのレコード フィールド ラベルは、メンバーの名前解決 (§14.1) で特別な役割を果たします。式の型は、レコード ラベルから推測できます。例: R = { dx : int; と入力します。dy: int } let fx = x.dx // x は R 型であると推測されます この例では、ルックアップ .dx はフィールド ルックアップに解決されます。

14.1.4 式で の名前解決 このセクションは少しあいまいに見えますが、この時点で名前解決を使用していると思います。最後に述べたように、複数ある場合は最初のアイテムを返します。

入力 long-ident、環境 env、および後続の型引数 < ,..., > の数のオプションのカウント n を指定すると、式の名前解決は long-ident< ,..の解釈を含む結果を計算します。 ., > 値またはその他の式アイテムとしての接頭辞、および残余パス残り。式での名前解決がどのように進行するかは、long-ident が単一の識別子であるか、複数の識別子で構成されているかによって異なります。long-ident が単一の識別子 ident の場合:

1. ExprItems テーブルで ident を検索します。結果と空の残りを返します。

2. ident が ExprItems テーブルに表示されない場合は、タイプ テーブルで検索し、利用可能な場合は n に一致する汎用アリティを使用します。この型と空の残りを返します。

3. ident が ExprItems テーブルにも Types テーブルにも表示されない場合は、失敗します。

...

式にあいまいさが含まれている場合、式の名前解決は、プロセスが生成した最初の結果を返します。

あなたが興味を持っている部分は、上記の最後の行です:「プロセスが生成する最初の結果を返します」。

于 2013-04-04T13:46:20.847 に答える
4

同様の名前のフィールドを持つレコード タイプが必要な場合、それらを区別する方法は次のとおりです。

let p1 = {myrec1.x = 1; y = 1}
于 2013-04-04T14:05:12.343 に答える
4

この動作は仕様です。それが F# の型推論の制限なのか、それとも一般的な型推論アルゴリズムの制限なのかはわかりません。あなたがそれについて考えるならば、しかし2つのオプションがあります:

  1. レコード式が与えられた場合{x = 1; y = 1}、「x」フィールドと「y」フィールドを最後のタイプと照合して、いずれかを宣言します。これは最も理解しやすく、F# コンパイラがレコード型の推論を実装する方法です。

  2. 現在のスコープ内の型のフィールドに基づいて、「最適」を決定してみてください。(これはあなたが尋ねていることだと思います。)

    ただし、このアルゴリズムは他の問題を引き起こす可能性があります。具体的には、レコード式の場合、 type の式を作成するつもりだったのか、またはの式を作成するつもりだったのにフィールドに値を代入するのを忘れていたの{x = 1; y = 1}か、コンパイラは判断できません。myrec1myrec2z

    また、まったく同じフィールドを持つ 2 つの型を宣言した場合、コンパイラは何をすべきでしょうか? たとえば、次のように追加するとどうなりますか。

    type myrec3 = {x: int; y: int}
    

言い換えれば、無料のランチなどというものはありません。型推論の「能力」を高めることはできますが、コンパイラから得られるエラー診断の精度がいくらか低下します。

于 2013-04-04T13:46:54.347 に答える
1

型注釈がないため、コンパイラはラベルからレコード型を推測しますが、最初の型のラベルは 2 番目の型のラベルと十分に区別できないため、後者が優先され、p1 は myrec2 型であると推測されます。型注釈を回避し、期待される型推論動作を得るために、異なるラベルを使用します。

type myrec1 = {x1: int; y: int}
type myrec2 = {x2: int; y: int; z: int}

let p1 = {x1 = 1; y = 1}
于 2013-04-04T13:47:56.873 に答える