Given:
open System.Linq
this is an acceptable expression:
[2; 3; 4].SelectMany(fun n -> { 1..n })
however this is not:
[2; 3; 4].SelectMany(fun n -> [ 1..n ])
The error message says:
int -> int list
is not compatible with type
int -> System.Collections.Generic.IEnumerable<'a>
F# is rejecting the expression because the function is returning an int list
.
Consider this C# program which does something similar:
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
returns a List<int>
and passing it to SelectMany
is acceptable to C#.
Is the F# behaviour by design or is it bug? If it's by design, why does the similar operation work in C#?
This is an expected behavior. C# generally does more automatic conversions between types than F#:
In C#, the result of the lambda function is List<T>
, but the C# compiler automatically converts the result to IEnumerable<T>
, which is the expected return type of the function.
In F#, the compiler does not automatically cast the result to IEnumerable<T>
and so your second snippet does not type check - because it is returning list<T>
which is a different type than the expected IEnumerable<T>
(you can cast list to enumerable, but they are different types).
The F# library defines its own version of the SelectMany
operation called Seq.collect
. The F# function has the following type:
> Seq.collect;;
val it : (('a -> #seq<'c>) -> seq<'a> -> seq<'c>)
Here, the type #seq<'c>
explicitly says that the result can be of any type that can be converted to seq<'c>
(this is what the #
in the name means). This is why the answer to your previous question works:
[2; 3; 4] |> Seq.collect (fun n -> [ 1..n ])
F# int list
is very different from C# List<int>
. If you wanted to use the type from C#, F# list is immutable (it cannot be changed after the list was created). On the other hand, in the first (working) example, you are returning a seq<int>
, which is basically same as IEnumerable<int>
.
F# list
implements IEnumerable
as well, but interfacing in F# works a bit differently: If you want to pass some argument as a specific interface, you need to explicitly cast it to that interface instance. That means, this would work as well:
let y =
[2; 3; 4].SelectMany(fun n -> [ 1..n ] :> IEnumerable<int>)
Alternatively, it is possible to convert F# list
to IEnumerable<>
using Seq.ofList
:
[2; 3; 4].SelectMany(fun n -> (Seq.ofList [ 1..n ]))
The other difference is explicit interfacing in F#:
Some other details on conversions from F# list to System.Collections.Generic.List<>
can be seen here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With