在F#中的序列表达式中键入推断

pom*_*a89 9 generics f# types inference seq

我想我不太明白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)
Run Code Online (Sandbox Code Playgroud)

如果我尝试用简单的序列表达式"产生"它们的实例,我会得到两个错误:

// 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
}
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为推断"常见"类型(界面,我认为,可以使这项工作更加困难)可能并不那么简单.但是,可以使用安全转换来修复这些错误:

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

如果我不想使用演员怎么办?我试图直接从"seq"指定序列类型,但事情似乎不起作用:

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

所以,我的问题是:有没有办法避免演员表?如果没有,是否有一个原因,即使指定类型不会使代码工作?

我尝试通过以下链接挖掘:

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

但我发现没有什么有用的......

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

kvb*_*kvb 7

这是一个很好的问题,答案可能比你到目前为止所得到的答案更复杂.例如,这确实有效:

let l : A list = [A(0); B(1); C(2)]
Run Code Online (Sandbox Code Playgroud)

但这个看似类似的代码不会:

let s : A seq = seq { yield A(0); yield B(1); yield C(2) }
Run Code Online (Sandbox Code Playgroud)

原因其实非常微妙.第二种情况涉及到基本上更复杂的版本:

let s : A seq = 
    Seq.append (Seq.singleton (A(0))) 
               (Seq.append (Seq.singleton (B(1))) 
                           (Seq.singleton (C(2)))))
Run Code Online (Sandbox Code Playgroud)

所以有什么问题?最终,问题是Seq.singleton具有泛型类型'x -> 'x seq,但我们希望在第二次调用中传递B并返回一个A seq(通过隐式地向上转换实例).F#隐式地一个具体类型的函数输入向上转换为具体的基类型(例如,如果Seq.singleton签名A -> A seq我们可以传递一个B!).不幸的是,通用函数不会发生这种情况(泛型,继承和类型推断不能很好地协同工作).


Gen*_*ski 6

为了解您混淆的原因,您不应该进一步,而不是您提到的链接的第一个声明:

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

你可以返回一个只有一个,相同类型的序列seq<A>,或者seq<obj>.OOP是事实,类型BC继承A是不相关的.以下内容可能会有所帮助:您的所有实例也都是从中继承的obj,但是为了从中创建,seq<obj>您应该明确地转换:

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

或者只是box像下面这样:

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

编辑:为了理解F#中显式转换背后的原因,以下(简单化)考虑可能会有所帮助.类型推断不做猜测; 除非它可以seq确定性地推导出类型,或者明确声明它,否则它会抱怨.

如果你这样做

let testSeq = seq {
   yield A(0)
   yield B(1)
   yield C(2)
}
Run Code Online (Sandbox Code Playgroud)

编译器带有不确定性 - testSeq可以是seq<A>,或者seq<obj>,所以它抱怨.当你这样做

let testSeq = seq {
   yield A(0)
   yield upcast B(1)
   yield upcast C(2)
}
Run Code Online (Sandbox Code Playgroud)

它根据第一个成员的类型推断testSeq,seq<A>并且在A不抱怨的情况下向上倾斜B和C. 同样,如果你这样做

let testSeq = seq {
   yield box A(0)
   yield upcast B(1)
   yield upcast C(2)
}
Run Code Online (Sandbox Code Playgroud)

它将推断testSeqseq<obj>基于所述第一构件的第二向上转型此时和第三成员的类型obj,而不是A.