int -> int list 与类型 int -> IEnumerable<'a> 不兼容。

4

给定:

open System.Linq

这是一个可接受的表达:

[2; 3; 4].SelectMany(fun n -> { 1..n })

然而,这并不是:
[2; 3; 4].SelectMany(fun n -> [ 1..n ])

错误信息显示:
    int -> int list    
is not compatible with type
    int -> System.Collections.Generic.IEnumerable<'a>

F#拒绝这个表达式,因为函数返回了一个int list

考虑这个类似的C#程序:

using System;
using System.Collections.Generic;
using System.Linq;

namespace SelectManyCs
{
    class Program
    {
        static List<int> Iota(int n)
        {
            var ls = new List<int>();

            for (var i = 0; i < n; i++) ls.Add(i);

            return ls;
        }

        static void Main(string[] args)
        {
            var seq = new List<int>() { 2, 3, 4 };

            foreach (var elt in seq.SelectMany(Iota))
                Console.WriteLine(elt);
        }
    }
}

Iota 返回一个 List<int>,并将其传递给 SelectMany 在 C# 中是可接受的。

F# 的行为是设计如此还是 bug?如果是设计如此,为什么类似的操作在 C# 中可以工作?


仅作澄清,C#程序中使用的列表类型与F#中使用的列表类型不同:System.Collections.Generic.List<T>Microsoft.FSharp.Collections.List<T> - Martin Liversage
2个回答

9
这是一种预期行为。C#通常在类型之间进行更多的自动转换,而不像F#那样:
  • 在C#中,lambda函数的结果是List<T>,但C#编译器会自动将结果转换为IEnumerable<T>,这是函数的预期返回类型。

  • 在F#中,编译器不会自动将结果强制转换为IEnumerable<T>,因此您的第二个代码片段无法通过类型检查-因为它返回的是list<T>,这是与预期的IEnumerable<T>不同的类型(您可以将列表转换为可枚举,但它们是不同的类型)。

F#库定义了自己版本的SelectMany操作,称为Seq.collect。F#函数具有以下类型:

> Seq.collect;;
val it : (('a -> #seq<'c>) -> seq<'a> -> seq<'c>) 

在这里,类型#seq<'c>明确表示结果可以是任何可以转换为seq<'c>的类型(这就是名称中#的含义)。 这就是为什么你之前问题的答案可以工作的原因:
[2; 3; 4] |> Seq.collect (fun n -> [ 1..n ])

1
“C#编译器会自动将结果转换为IEnumerable<T>”这种说法是否恰当?由于List类实现了IEnumerable,所以List实际上就是一个IEnumerable,因此不需要进行转换。 - dharmatech
文档 表明 F# List 实现了 IEnumerable。因此不需要进行强制转换或转换。 - dharmatech
@dharmatech 你说得对 - 它只是将列表强制转换为 IEnumerable<T>(而不进行任何转换)。但即使是强制转换也只在某些情况下自动完成... - Tomas Petricek
在表达式 [2;3;4].SelectMany(...) 中,SelectMany(定义在 IEnumerable 上)直接在 F# 列表上调用,没有进行任何强制转换或转换。因此我认为这里的设计不一致。 :-) - dharmatech
F#编译器仅在方法参数(包括实例参数)上自动执行转换。因此它有点不一致,但这是好的 :-) 它允许在大多数重要情况下进行操作,而不会使类型推断变得太困难... - Tomas Petricek
2
啊哈...你关于#seq<'c>中的#的注释让我去阅读了F#文档中的Flexible Types。我之前不知道这个标注。谢谢! - dharmatech

4

F#int list与C#List<int>非常不同。如果你想使用来自C#的类型,那么F#列表是不可变的(在创建列表后无法更改)。另一方面,在第一个(有效的)示例中,您正在返回一个seq<int>,这基本上与IEnumerable<int>相同。 F#list也实现了IEnumerable,但在F#中接口有点不同:如果你想将某些参数作为特定接口传递,你需要将其显式转换为该接口实例。这意味着以下内容也将起作用:

let y = 
    [2; 3; 4].SelectMany(fun n -> [ 1..n ] :> IEnumerable<int>)

另外,也可以使用 Seq.ofList 将 F# 的 list 转换为 IEnumerable<>:

[2; 3; 4].SelectMany(fun n -> (Seq.ofList [ 1..n ]))

另一个区别是F#中的显式接口: 有关从F#列表转换为System.Collections.Generic.List<>的其他详细信息,请参见此处

2
System.Collections.Generic.List 确实与 F# 列表非常不同,但这种差异与此处的问题无关。在 F# 中使用 System.Collections.Generic.List 会像使用 F# 列表一样表现,并且需要相同的“转换”才能正常工作。请注意,F# 列表实现了 seq(又名 IEnumerable),就像 .net 列表一样。 - sepp2k
@sepp2k,我改进了答案,以使这一点更加清晰。 - Tomas Pastircak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接