2012-01-17 8 views
3

私は再帰的に定義された2つのクラスを1つの順序でコンパイルできますが、別の順序ではコンパイルできません。次のようにコードの簡略化されたバージョンが見える:F#:一般的なメンバは、このプログラムポイントの前に一様でないインスタンス化で使用されました

type Loader() = 
    member this.Load path = 
     let ref = Ref() 
     ref.Wait() 
     ref 

and Ref<'T>(data: obj) = 
    member this.Foo = 1 
    member this.Wait() =() 
    member this.Value = data :?> 'T 

エラーがthis.Wait()宣言です。 - しかし、実際のコードは再帰的であることを2つの宣言を必要としない簡易版で実際に再帰のためには必要がないことを

asset.fs(11,21): error FS1198: The generic member 'Wait' has been used at a non-uniform 
instantiation prior to this program point. Consider reordering the members so this 
member occurs first. Alternatively, specify the full type of the member explicitly, 
including argument types, return type and any additional generic parameters 
and constraints. 

注:以下のようにフルテキストがあります。

エラーメッセージ(つまり、メンバthis.Wait(): unit =())に記載されているように、Wait()の型を指定することでこれを解決できますが、なぜこれを行う必要があるのか​​を理解したいと思います。

答えて

3

あなたの質問に答えるために、私は@Brianによってthis awesome answer引用:

非常にしばしば「修正」は単に「宣言されているいくつかの重要な方法に( 戻り値の型を含む)完全な一型シグネチャを追加することです「遅い」が「 」と呼ばれていました(メンバーの集合の間に前方参照を導入しました)。相互再帰的なクラスのあなたの場合は

ref.Wait()が呼び出されたときに、F#の型推論は、型Refの宣言とWaitの型シグネチャを解決するために、その最初のメンバーになります。現時点で入手可能な情報はないため、Waitの署名は間違ってunit -> 'aと推定されます。見上げるクラスの最初のメンバーは、次の例に基づいて、私の観察である:

type Loader() =  
    member this.Load path = 
     let ref = Ref() 
     ref.Wait()   
     ref 
    member this.Load2 path = 
     let ref = Ref() 
     ref.Wait2()   
     ref 

and Ref<'T>(data: obj) =   
    member this.Wait() =() // resolve correctly 
    member this.Wait2() =() // fail to resolve  
    member this.Foo = 1 
    member this.Value = data :?> 'T 

はところで、この制限を回避するために多くの方法があります:

  1. 宣言-後半のための型注釈を提供するには、とコール早期メソッド。
  2. WaitRefの最初のメンバーとして宣言します(上記の例のようにスケーラビリティはありません)。
  3. 相互回帰ペアの最初の型としてRefを宣言します。 RefLoaderから多くの情報を必要とするため、を解決するための情報を最初にRefに解決することをお勧めします。
+0

質問は、呼び出しの場所での最初の推論のWaitのタイプは何ですか。私はそれがunit - > unit(上記のTomasの答えを参照)であり、unit - > 'aではないことを期待しています。 – zeuxcg

+1

ちょうどチェック - 呼び出し元コードをref.Wait()に変更すると、ユニットは問題を修正します。そうすれば、Waitタイプはユニットになると推測され、後でユニット - >ユニットに制約されます。宣言は認められません。ありがとう! – zeuxcg

+0

@zeuxcg: 'let()= ref.Wait2()'に行を変更してもエラーは修正されませんが、この構造体の拘束力は明示的な ':unit'の拘束力以上でなければなりません。怪しいものが起こっている。コンパイラの型推論には微妙なバグがあると思います。 – kkm

2

私は行動を説明するために型チェックについて十分に知らない(と私はそれだけで120ページの「再帰タイプ」について簡単に言及しているためF# specificationは、これを説明するとは思いません)。

とにかく、コンパイラは再帰型の定義を1つのグループとしてチェックするので、その型を(部分的に)推論するためにWaitのようなメソッドの使用が使われます。型を明示的に指定すると、明示的な定義が推論された定義をオーバーライドします。

この例では、Ref<'T>がジェネリック型である場合にのみ問題が表示されます。Waitを呼び出すと、コンパイラはメソッドの型がunit -> unitであると正確に推測しますが、メソッドの('Tの場合)のthisパラメータに柔軟性があると思います。 compiler source codeに基づい

、エラーがWait方法の定義を処理するときthis変数に関連するいくつかの制約を追加すること(ライン8914)に失敗した場合にのみ報告されています。だから私の推測では、Loadの中のrefの値に関連するいくつかの制約が、Wait(コンパイラがメソッドをチェックし、そのタイプを確実に知るための完全なアノテーションを持たないとき)から生成された制約と衝突するということです。

+0

あなたにバグのように見えますか?あるいは、型システムの本当の不完全さ?しかし、私はそのタイプが推論できない理由は分かりません。 – kkm

+0

私はWait()がユニットであると推測されていると思っています - > 'コールサイトで、受け入れられた答えとそれに対する私のコメントを見てください。 – zeuxcg

+0

@zeuxcgこれは 'this'リファレンスに何らかの形で関係していると確信していますが、どのように(コンパイラコードでエラーが報告されるのか)は分かりません。 F#は、戻り値の型が 'unit'であると推論することができます。' let rec foo a = bar a; 1 とbar a = a'の場合、 'bar'は' unit'を返すと推測されます( 'foo'の使用から)。 –

関連する問題