假设:
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#中起作用?
答案 0 :(得分:9)
这是预期的行为。 C#通常在类型之间进行比F#更多的自动转换:
在C#中,lambda函数的结果是List<T>
,但C#编译器会自动将结果转换为IEnumerable<T>
,这是函数的预期返回类型。
在F#中,编译器不会自动将结果转换为IEnumerable<T>
,因此您的第二个代码段不会键入check - 因为它返回的list<T>
与预期的类型不同IEnumerable<T>
(您可以将列表转换为可枚举,但它们是不同的类型)。
F#库定义了自己的SelectMany
操作版本,名为Seq.collect
。 F#函数具有以下类型:
> Seq.collect;;
val it : (('a -> #seq<'c>) -> seq<'a> -> seq<'c>)
此处,类型#seq<'c>
明确表示结果可以是任何可以转换为seq<'c>
的类型(这就是名称中#
的含义)。这就是answer to your previous question有效的原因:
[2; 3; 4] |> Seq.collect (fun n -> [ 1..n ])
答案 1 :(得分:4)
F#int list
与C#List<int>
非常不同。如果要使用C#中的类型,F#list是不可变的(在创建列表后无法更改)。另一方面,在第一个(工作)示例中,您将返回seq<int>
,它与IEnumerable<int>
基本相同。
F#list
也实现了IEnumerable
,但F#中的接口工作方式略有不同:如果要将某个参数作为特定接口传递,则需要将其显式转换为该接口实例。这意味着,这也可行:
let y =
[2; 3; 4].SelectMany(fun n -> [ 1..n ] :> IEnumerable<int>)
或者,可以使用list
将F#IEnumerable<>
转换为Seq.ofList
:
[2; 3; 4].SelectMany(fun n -> (Seq.ofList [ 1..n ]))
另一个区别是F#中的显式接口:
有关从F#列表转换为System.Collections.Generic.List<>
的其他详细信息,请参阅here。