9

我想我不太明白 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/

但是我没有发现任何有用的...

提前感谢您提供的任何答案:)

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

原因其实很微妙。第二种情况脱糖,基本上是一个更复杂的版本:

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,但我们想在第二次调用中传递 aB并取回a A seq(通过隐式向上转换实例)。F#隐式地将一个具体类型的函数输入向上转换为具体的基类型(例如,如果Seq.singleton有签名A -> A seq,我们可以传递一个B!)。不幸的是,泛型函数不会发生这种情况(泛型、继承和类型推断不能很好地结合在一起)。

于 2013-05-23T15:48:17.413 回答
6

为了了解您的困惑的原因,您不应该比您提到的链接的第一个陈述更进一步:

序列是元素的逻辑系列,都是一种类型

您可以返回一个只有一个、相同类型的序列,如seq<A>、 或seq<obj>B类型和C继承自的 OOP-ish 事实无关紧要A。以下内容可能会有所帮助:您的所有实例也都继承自obj,但为了从它们中生成 aseq<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)
}

它根据第一个成员的类型进行推断testSeqseq<A>并将 B 和 C 向上转换为A没有抱怨。同样,如果你这样做

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

它将根据第一个成员的类型进行推断testSeqseq<obj>这次将第二个和第三个成员向上转换为obj,而不是A

于 2013-05-23T13:27:57.227 回答
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 所指出的,对我的问题的简短回答是否定的,在我描述的场景中无法避免强制转换。首先,文档本身指出:

序列是元素的逻辑系列,都是一种类型

此外,在下一种情况下:

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

type Derived1(x) =
    inherit Base(x)

type Derived2(x) =
    inherit Base(x)

我们当然知道Derived1or 的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.singletonBase -> 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
}

我还尝试扩展自定义构建器,使其支持更复杂的构造,例如forand while,但我尝试编写 while 处理程序失败。如果有人感兴趣,这可能是一个有用的研究方向。

感谢所有回答的人:)

于 2013-05-25T07:38:20.680 回答