簡體   English   中英

在F#中的序列表達式中鍵入推斷

[英]Type inference in sequence expressions in F#

我想我不太明白F#如何推斷序列表達式中的類型以及為什么類型不能正確識別,即使我直接從“seq”指定了元素的類型。

在下面的F#代碼中,我們有一個基類A和兩個派生類,B和C:

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

type B(x) =
    inherit A(x)

type C(x) =
    inherit A(x)

如果我嘗試用簡單的序列表達式“產生”它們的實例,我會得到兩個錯誤:

// 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/

但我發現沒有什么有用的......

提前感謝您提出的任何答案:)

這是一個很好的問題,答案可能比你到目前為止所得到的答案更復雜。 例如,這確實有效:

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

但這個看似類似的代碼不會:

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

原因其實非常微妙。 第二種情況涉及到基本上更復雜的版本:

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

所以有什么問題? 最終,問題是Seq.singleton具有泛型類型'x -> 'x seq ,但我們想要傳遞B並在第二次調用中返回A seq (通過隱式地向上轉換實例)。 F# 隱式地一個具體類型的函數輸入向上轉換為具體的基類型(例如,如果Seq.singleton具有簽名A -> A seq我們可以傳遞B !)。 不幸的是,通用函數不會發生這種情況(泛型,繼承和類型推斷不能很好地協同工作)。

為了解您混淆的原因,您不應該進一步,而不是您提到的鏈接的第一個聲明:

序列是一種類型的邏輯系列元素。

您可以返回僅一個序列,相同類型,如seq<A>seq<obj> 事實上,類型BC是從A繼承的, A是不相關的。 以下內容可能有所幫助:您的所有實例也都是從obj繼承的,但是為了從它們中獲取seq<obj>您應該顯式地轉換:

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

或者只是將它們box如下:

// Works fine too
let testSeq = seq {
    yield box (A(0))
    yield box (B(1))
    yield box (C(2))
}

編輯 :為了理解F#中顯式轉換背后的原因,以下(簡單化)考慮可能會有所幫助。 類型推斷不做猜測; 除非它可以確定性地推導出seq類型,或者明確聲明它,否則它會抱怨。

如果你這樣做

let testSeq = seq {
   yield A(0)
   yield B(1)
   yield C(2)
}

編譯器帶有不確定性 - testSeq可以是seq<A> ,也可以是seq<obj> ,所以它會抱怨。 當你這樣做

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

它根據第一個成員的類型將testSeq推斷為seq<A> ,並且A沒有抱怨A情況下將B和C向上轉換為A 同樣,如果你這樣做

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

它會根據第一個成員的類型將testSeq推斷為seq<obj> ,這次將第二個和第三個成員向上轉換為obj ,而不是A

有一個在F#檢查沒有隱含上溯造型在這里 您可以嘗試推斷上傳。

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
    }

提問者已經接受了答案,但以下內容可能有用。 關於“有沒有辦法避免強制轉換”的問題,我想補充一點:嚴格使用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

請注意,這個答案並不是特別編譯時安全,不太確定是否可以完成(煩人的通用約束)。

這只是我的問題收到的所有答案的摘要,以便未來的讀者可以通過閱讀(並決定是否閱讀其他答案以獲得更好的洞察力)來節省時間。

正如@Gene Belitski所指出的那樣,對我的問題的簡短回答是否定的,在我描述的場景中不可能避免演員表。 首先,文檔本身表明:

序列是一種類型的邏輯系列元素。

而且,在下一個情況下:

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

type Derived1(x) =
    inherit Base(x)

type Derived2(x) =
    inherit Base(x)

我們肯定有Derived1Derived2的實例也是Base的實例,但這些實例也是obj實例也是如此。 因此,在以下示例中:

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

我們有,正如@Gene Belitski所解釋的那樣,編譯器無法在Baseobj之間選擇正確的祖先。 這些決定可以通過強制轉換來幫助,如下面的代碼所示:

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 (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
}

我也嘗試擴展自定義構建器,以便它支持更復雜的構造,如forwhile ,但我嘗試編寫while處理程序失敗了。 如果有人感興趣,這可能是一個有用的研究方向。

謝謝大家回答:)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM