这是一个F#编译器错误吗? #2

时间:2010-10-04 14:37:28

标签: compiler-construction f# ienumerable for-loop

open System

type Foo() =
     interface Collections.IEnumerable with
         member x.GetEnumerator () = null

type Bar() =
     interface Collections.IEnumerable with
         member x.GetEnumerator () = null
     interface Collections.Generic.IEnumerable<int> with
         member x.GetEnumerator () = null

let xs, ys = Foo(), Bar()

for x in xs do () // <--
for y in ys do () // fine

上面的代码产生以下编译错误:

The type 'Foo' is not a type whose values can be enumerated with this syntax, i.e. is not compatible with either seq<_>, IEnumerable<_> or IEnumerable and does not have a GetEnumerator method.

代码看起来完全合法,通用版本工作正常。这是一个F#编译器错误吗?

3 个答案:

答案 0 :(得分:5)

我认为这是错误消息和规范之间的不匹配。正如 kvb 指出的那样,规范仅在两种情况下允许for ... in

  • 当类型实现通用IEnumerable<_>接口(又名seq<_>
  • 当类型具有返回具有特定属性的类型的GetEnumerator方法时

如果类型实现非通用IEnumerable接口,则它与这两个条件中的任何一个都不匹配。但是,如果将其强制转换为IEnumerable,那么它实际上将是IEnumerable类型,它与第二个条件匹配。在类型中直接使用GetEnumerator成员(如desco建议的那样)也是正确的,因为它也匹配第二种情况。

因此,我认为错误消息不正确,因为它表示实现非泛型IEnumerable就足够了,但实际上并非如此。

但是,似乎有一个与for循环相关的实际编译器错误。编写以下代码时会出现编译器“内部错误”(这是不正确的,因为推断的泛型返回类型没有实现IEnumerator):

 type Foo() =
   member x.GetEnumerator () = null
 for x in Foo() do ()  // Internal error here

答案 1 :(得分:5)

您的样本可以简化为

type Foo() =
 interface Collections.IEnumerable with
     member x.GetEnumerator () = null

for x in Foo() do ()

最初,F#编译器试图断言源类型正在实现IEnumerable&lt; _&gt; 在此断言失败后 - 它搜索可访问的GetEnumerator / 0方法,该方法返回具有可访问的MoveNext()/ Current成员的类型。似乎来自IEnumerable的显式实现的方法在Foo类型中是不可见的,因为下面的代码是有效的:

open System
open System.Collections

type Foo() =
    member x.GetEnumerator () : IEnumerator = null

for x in Foo() do () // GetEnumerator is accessible in Foo

open System
open System.Collections

type Foo() =
    interface IEnumerable with
        member x.GetEnumerator () : IEnumerator = null

for x in (Foo() :> IEnumerable) do () // IEnumerable has accessible GetEnumerator

答案 2 :(得分:3)

我不这么认为,但这不是一个非常有用的错误信息。有关如何评估for ... in ... do ...表达式的详细信息,请参阅规范的Sequence Iteration Expressions部分。如果类型实现IEnumerable<_>,则模式正如预期的那样工作。否则,编译器会使用正确的签名查找公共(规范说“可访问”)GetEnumerator方法并调用它。由于F#接口实现是显式的,因此如果不将GetEnumerator类型向上转换为Foo,则无法使用IEnumerable方法。如果您执行upcast,您的代码将再次按预期工作:

for x in (xs :> Collections.IEnumerable) do () // fine