[英]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-inference-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>
。 事实上,类型B
和C
是从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)
我们肯定有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 (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处理程序失败了。 如果有人感兴趣,这可能是一个有用的研究方向。
谢谢大家回答:)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.