Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

int -> int list is not compatible with type int -> IEnumerable<'a>

Tags:

c#

.net

linq

f#

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#?

like image 400
dharmatech Avatar asked Dec 12 '22 06:12

dharmatech


2 Answers

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 ])
like image 62
Tomas Petricek Avatar answered Dec 13 '22 21:12

Tomas Petricek


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.

like image 29
Tomas Pastircak Avatar answered Dec 13 '22 21:12

Tomas Pastircak