37

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?

  • I know I can use System.Linq in F# if I want to.
  • I know I can make a recursive method and use F# Computation Expressions (and make even more powerful things).

But if I try to prove that F# List operations are more powerful than LINQ...

  • .Where = List.filter
  • .Select = List.map
  • .Aggregate = List.fold
  • ...

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
Tuomas Hietanen
  • 4,650
  • 2
  • 35
  • 43
  • 2
    How would you show that F# list is more powerful than C# list? If it is a matter of which one has more functions for it, that doesn't show more powerful, just more bloated. Are you trying to show that there is something you can do in F# that you can't with C#, using lists? – James Black Jan 05 '11 at 00:46

5 Answers5

39

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
Community
  • 1
  • 1
gradbot
  • 13,732
  • 5
  • 36
  • 69
13

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 };
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
5

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

Juliet
  • 80,494
  • 45
  • 196
  • 228
4

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 ....  };
Khanzor
  • 4,830
  • 3
  • 25
  • 41
  • I don't see `Seq.bind` in either the F# library or the powerpack. Did you mean `Seq.collect`? – Juliet Jan 05 '11 at 01:19
  • You don't see `Seq.bind` because it's not a function in the `Seq` module; its a method of the workflow builder underlying the `seq` monoid. – pblasucci Jan 05 '11 at 01:59
  • 4
    @pblasucci: In principle yes, but `seq { .. }` isn't a real computation expression. It is handled specially by the compiler and `seq` isn't really a computation builder (it is just a function `seq<'a> -> seq<'a>`. So, `Seq.collect` is the right answer but it is not really used in the compiled form of the sequence expression. – Tomas Petricek Jan 05 '11 at 03:36
  • 1
    This looks like a bit more imperative than the declarative SelectMany – Tuomas Hietanen Jan 05 '11 at 11:25
  • @Tomas: Thanks for the clarification. Is the special-case handling of `seq` because it occurs so frequently, thus needing special optimization? – pblasucci Jan 05 '11 at 13:45
0

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.

aloisdg
  • 22,270
  • 6
  • 85
  • 105