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