Reputation: 5323
This is quite simple question but I didn't find an answer:
Is there any Seq/List operation in F# to match the LINQ SelectMany?
But if I try to prove that F# List operations are more powerful than LINQ...
In C# SelectMany usage syntax is pretty simple:
var flattenedList = from i in items1
from j in items2
select ...
Is there any easy direct match, List.flatten, List.bind or something like that?
SelectMany has a couple of signatures, but the most complex one seems to be:
IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
this IEnumerable<TSource> source,
Func<TSource, IEnumerable<TCollection>> collectionSelector,
Func<TSource, TCollection, TResult> resultSelector
);
In F# terms this would be:
('a -> 'b list) -> ('a -> 'b -> 'c) -> 'a list -> 'c list
Upvotes: 37
Views: 11905
Reputation: 23521
There are a lot of good options to create your own SelectMany
, but what about using SelectMany
directly?
let flatten (source : 'T seq seq) :'T seq =
System.Linq.Enumerable.SelectMany(source, id)
This is a basic call to the .net SelectMany
with the F# id
function.
Upvotes: 0
Reputation: 81516
Other posts show how to match the linq with
Starting with this linq:
var flattenedList = from i in items1
from j in items2
select ...
var flattenedList2 = items1.SelectMany(i => items2.Map(j => ...))
Equivalent F# is:
let flattenedList = seq {
for a in items1 do
for b in items2 do
yield ... }
let flattenedList2 = items1 |> Seq.collect (fun i -> items2 |> Seq.map (fun j -> ...))
The two bits of code are roughly equivalent in expressiveness and complexity.
With that said, let's address a specific comment in your post:
But if I try to prove that F# List operations are more powerful than LINQ...
Operations in the Seq/List modules are roughly equivalent to the Enumerable/Linq extensions.
However, I'd say the killer feature for lists is the ability to pattern match on them. Here's a silly example which doesn't convert easily to linq:
let rec funky = function
| x::y::z::rest -> (z, y)::funky(z::x::rest)
| [y;z]-> [(z, y)]
| [z] -> [(z, z)]
| [] -> []
// funky [1..6]
// = (int * int) list = [(3, 2); (4, 1); (5, 3); (6, 4)]
This would be a bit awkward to reimplement in C#, but its dead simple to write F#.
Upvotes: 5
Reputation: 13862
collect
is the F# equivalent of SelectMany however it doesn't provide all the overloads. Here's how to make the one you referenced.
let selectMany (ab:'a -> 'b seq) (abc:'a -> 'b -> 'c) input =
input |> Seq.collect (fun a -> ab a |> Seq.map (fun b -> abc a b))
// gives
// val selectMany : ('a -> seq<'b>) -> ('a -> 'b -> 'c) -> seq<'a> -> seq<'c>
I believe F# doesn't provide all the SelectMany overloads because they would add noise to the library. Here's all four overloads to SelectMany in Microsoft Naming.
let selectMany (source : 'TSource seq) (selector : 'TSource -> 'TResult seq) =
source |> Seq.collect selector
let selectMany (source : 'TSource seq) (selector : 'TSource -> int -> 'TResult seq) =
source |> Seq.mapi (fun n s -> selector s n) |> Seq.concat
let selectMany (source : 'TSource)
(collectionSelector : 'TSource -> 'TCollection seq)
(resultSelector : 'TSource -> 'TCollection -> 'TResult) =
source
|> Seq.collect (fun sourceItem ->
collectionSelector sourceItem
|> Seq.map (fun collection -> resultSelector sourceItem collection))
let selectMany (source : 'TSource)
(collectionSelector : 'TSource -> int -> 'TCollection seq)
(resultSelector : 'TSource -> 'TCollection -> 'TResult) =
source
|> Seq.mapi (fun n sourceItem ->
collectionSelector sourceItem n
|> Seq.map (fun collection -> resultSelector sourceItem collection))
|> Seq.concat
"F# List operations are more powerful than LINQ..." While seq / list operations are great some real "F# power" comes from Function Composition and Currying.
// function composition
let collect selector = Seq.map selector >> Seq.concat
Upvotes: 39
Reputation: 292405
You can use List.collect or Seq.Collect:
let items1 = [1; 2; 3]
let items2 = [4; 5; 6]
let flat = items1 |> List.collect (fun i1 -> items2 |> List.map (fun i2 -> [i1, i2]))
That would be roughly equivalent to the following C# code:
var flat = from i1 in items1
from i2 in items2
select new { i1, i2 };
Upvotes: 13
Reputation: 5000
Seq.bind
is what you want. SelectMany
is really just a monadic bind :).
So you'd do:
seq { for i in items1 do
for j in items2 do
yield .... };
Upvotes: 4