9

F# がシーケンス式の型をどのように推論するのか、また、「seq」から要素の型を直接指定しても型が正しく認識されない理由がよくわかっていないと思います。

次の F# コードには、基本クラス A と 2 つの派生クラス B と C があります。

type A(x) =
    member a.X = x

type B(x) =
    inherit A(x)

type C(x) =
    inherit A(x)

単純なシーケンス式でそれらのインスタンスを「生成」しようとすると、次の 2 つのエラーが発生します。

// Doesn't work, but it makes sense.
let testSeq = seq {
    yield A(0)
    yield B(1) // Error, expected type: A
    yield C(2) // Error, expected type: A
}

「一般的な」タイプを推測するのはそれほど簡単ではないかもしれないので、それは理にかなっています(インターフェースは、それをはるかに困難にする可能性があると思います)。ただし、これらのエラーは安全なキャストで修正できます。

// Works fine :)
let testSeqWithCast = seq {
    yield A(0)
    yield B(1) :> A
    yield C(2) :> A
}

キャストを使用したくない場合はどうすればよいですか? 「seq」からシーケンスタイプを直接指定しようとしましたが、うまくいかないようです:

// Should work, I think...
let testGenSeq = seq<A> {
    yield A(0)
    yield B(1) // Error, expected type: A
    yield C(2)
}

だから、私の質問は: キャストを避ける方法はありますか? そうでない場合、型を指定してもコードが機能しない理由はありますか?

次のリンクを掘り下げてみました:

http://msdn.microsoft.com/en-us/library/dd233209.aspx http://lorgonblog.wordpress.com/2009/10/25/overview-of-type-in​​ference-in-f/

しかし、私は何も役に立たなかった...

あなたが与えることができるあらゆる種類の答えを前もってありがとう:)

4

5 に答える 5

7

これは良い質問です。答えは、これまでに得た回答よりもおそらく複雑です。たとえば、これは機能します:

let l : A list = [A(0); B(1); C(2)]

しかし、この一見類似したコードはそうではありません:

let s : A seq = seq { yield A(0); yield B(1); yield C(2) }

その理由は実は非常に微妙です。2 番目のケースは、基本的に次のより複雑なバージョンの脱糖です。

let s : A seq = 
    Seq.append (Seq.singleton (A(0))) 
               (Seq.append (Seq.singleton (B(1))) 
                           (Seq.singleton (C(2)))))

だから問題は何ですか?最終的に、問題はSeq.singletonがジェネリック型を持っていることですが、(インスタンスを暗黙的にアップキャストすることにより) 2 番目の呼び出しでa を渡し、 an を取得'x -> 'x seqしたいと考えています。F#は、1 つの具象型の関数入力を具象基本型に暗黙的にアップキャストします(たとえば、署名があれば ! を渡すことができます)。残念ながら、これはジェネリック関数では起こりません (ジェネリック、継承、および型推論はうまく連携しません)。BA seqSeq.singletonA -> A seqB

于 2013-05-23T15:48:17.413 に答える
3

F# check hereには暗黙的なアップキャストはありません。推論されたアップキャストを試すことができます。

let testSeq : seq<A> = seq {
    yield A(0)
    yield upcast B(1)
    yield upcast C(2)
    }

または、それで十分な場合は、判別共用体を使用できます。

type X =
    | A of int
    | B of int
    | C of int

let testSeq = seq {
    yield A 0
    yield B 1
    yield C 2
    }
于 2013-05-23T13:41:19.367 に答える
2

質問者はすでに回答を受け入れていますが、以下が役立つ場合があります。「キャストを回避する方法はありますか」という問題について、追加したいと思います。厳密に使用するとseq、答えはすでに与えられているとおりです(不可能です)。

ただし、独自の「ワークフロー」を作成することもできます。何かのようなもの:

  open Microsoft.FSharp.Collections;

  let s = seq<string>

  type A(x) =
      member a.X = x

  type B(x) =
      inherit A(x)

  type C(x) =
      inherit A(x)

  type MySeq<'a>() =
     member this.Yield(item: 'a): seq<'a> =
        Seq.singleton item
     member this.Yield(item: 'b): seq<'a> =
        Seq.singleton ((item :> obj) :?> 'a)
     member this.Combine(left, right) : seq<'a> =
        Seq.append left right
     member this.Delay (fn: unit -> seq<'a>) = fn()

  [<EntryPoint>]
  let main argv = 

      let myseq = new MySeq<A>()
      let result = myseq {
        yield A(1)
        yield B(2)
      }

      0

この回答は特にコンパイル時に安全ではないことに注意してください。それが可能かどうかはよくわかりません(厄介な一般的な制約)。

于 2013-05-23T13:59:42.050 に答える
0

これは、私の質問に対するすべての回答の要約に過ぎないため、将来の読者はこれを読むことで時間を節約できます (より良い洞察を得るために他の回答を読むかどうかを判断してください)。

@Gene Belitski が指摘したように、私の質問に対する短い答えはノーです。私が説明したシナリオでキャストを避けることはできません。まず、ドキュメント自体には次のように記載されています。

シーケンスは、すべてが 1 つのタイプの論理的な一連の要素です。

さらに、次のような状況では:

type Base(x) =
    member b.X = x

type Derived1(x) =
    inherit Base(x)

type Derived2(x) =
    inherit Base(x)

Derived1または のインスタンスは のインスタンスDerived2でもあることは確かですがBase、これらのインスタンスが のインスタンスでもあることも事実ですobj。したがって、次の例では:

let testSeq = seq {
   yield Base(0)
   yield Derived1(1) // Base or obj?
   yield Derived2(2) // Base or obj?
}

@Gene Belitski で説明されているように、コンパイラは と の間Baseで正しい先祖を選択できないことがわかりましたobj。このような決定は、次のコードのようにキャストを使用して行うことができます。

let testBaseSeq = seq<Base> {
    yield Base(0)
    yield upcast Derived1(1)
    yield upcast Derived2(2)
}

let testObjSeq = seq<obj> {
    yield Base(0) :> obj
    yield Derived1(1) :> obj
    yield Derived2(2) :> obj
}

ただし、さらに説明する必要があります。@kvb が述べているように、これがキャストなしでは機能しない理由は、ジェネリック、継承、および型推論を暗黙的に混合しているため、期待どおりに連携しない可能性があるためです。に表示されるスニペットは、次のtestSeqように自動的に変換されます。

let testSeq = Seq.append (Seq.singleton (Base(0)))
                         (Seq.append (Seq.singleton (Derived1(1)))
                                     (Seq.singleton (Derived2(2))))

問題は にSeq.singletonあり、自動アップキャストが必要になります ( のようにSeq.singleton (Derived1(1))) が、汎用であるため実行できませんSeq.singleton。たとえば、 の署名が でSeq.singletonあった場合、Base -> Base seqすべてが機能していたはずです。

@Marcus は、自分のシーケンスビルダーを定義するために要約する私の質問に対する解決策を提案しています。次のビルダーを書いてみました:

type gsec<'a>() =
    member x.Yield(item: 'a) = Seq.singleton item
    member x.Combine(left, right) = Seq.append left right
    member x.Delay(fn: unit -> seq<'a>) = fn()

そして、私が投稿した簡単な例はうまく機能しているようです:

type AnotherType(y) =
    member at.Y = y

let baseSeq = new gsec<Base>()
let result = baseSeq {
    yield Base(1)        // Ok
    yield Derived1(2)    // Ok
    yield Derived2(3)    // Ok
    yield AnotherType(4) // Error, as it should
    yield 5              // Error, as it should
}

forや などのより複雑な構造をサポートするようにカスタム ビルダーを拡張しよwhileうとしましたが、 while ハンドラーを記述しようとして失敗しました。誰かが興味を持っているなら、それは有用な研究の方向性かもしれません.

答えてくれたみんなありがとう:)

于 2013-05-25T07:38:20.680 に答える