【问题标题】:F# List SelectManyF# 列表选择多
【发布时间】:2011-06-03 17:37:39
【问题描述】:

这是一个很简单的问题,但我没有找到答案:

F# 中是否有任何 Seq/List 操作来匹配 LINQ SelectMany?

  • 我知道如果我可以在 F# 中使用 System.Linq 想要。
  • 我知道我可以做一个递归方法 并使用 F# 计算表达式 (并制造更强大的东西)。

但如果我试图证明 F# List 操作比 LINQ 更强大...

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

在 C# 中,SelectMany 的使用语法非常简单:

var flattenedList = from i in items1
                    from j in items2
                    select ...

有没有简单的直接匹配,List.flatten,List.bind 之类的?

SelectMany 有几个签名,但最复杂的似乎是:

IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, IEnumerable<TCollection>> collectionSelector, 
    Func<TSource, TCollection, TResult> resultSelector
);

在 F# 术语中,这将是:

('a -> 'b list) -> ('a -> 'b -> 'c) -> 'a list -> 'c list

【问题讨论】:

  • 您如何证明 F# 列表比 C# 列表更强大?如果是哪个功能更多的问题,那并没有表现出更强大,只是更臃肿。您是否想通过使用列表来证明您可以在 F# 中做一些您在 C# 中无法做到的事情?

标签: f# linq


【解决方案1】:

创建自己的SelectMany 有很多不错的选择,但是直接使用SelectMany 呢?

let flatten (source : 'T seq seq) :'T seq =
    System.Linq.Enumerable.SelectMany(source, id)

这是使用 F# id function 对 .net SelectMany 的基本调用。

【讨论】:

    【解决方案2】:

    其他帖子显示如何将 linq 与

    匹配

    从这个 linq 开始:

    var flattenedList = from i in items1
                        from j in items2
                        select ...
    var flattenedList2 = items1.SelectMany(i => items2.Map(j => ...))
    

    等效的 F# 是:

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

    这两段代码在表现力和复杂度上大致相当。

    话虽如此,让我们在您的帖子中解决一个特定的评论:

    但如果我试图证明 F# List 操作比 LINQ...

    Seq/List 模块中的操作大致相当于 Enumerable/Linq 扩展。

    不过,我想说列表的杀手级功能是能够对列表进行模式匹配。这是一个不容易转换为 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)]
    

    这在 C# 中重新实现会有点尴尬,但编写 F# 非常简单。

    【讨论】:

      【解决方案3】:

      collect 是 SelectMany 的 F# 等价物,但它不提供所有重载。以下是您引用的制作方法。

      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>
      

      我相信 F# 并没有提供所有的 SelectMany 重载,因为它们会给库添加噪音。这是 Microsoft Naming 中 SelectMany 的所有四个重载。

      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# 列表操作比 LINQ 更强大...”虽然 seq / 列表操作很棒,但一些真正的“F# 功能”来自 Function CompositionCurrying

      // function composition
      let collect selector = Seq.map selector >> Seq.concat
      

      【讨论】:

        【解决方案4】:

        您可以使用 List.collect 或 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]))
        

        这大致相当于以下 C# 代码:

        var flat = from i1 in items1
                   from i2 in items2
                   select new { i1, i2 };
        

        【讨论】:

          【解决方案5】:

          Seq.bind 是你想要的。 SelectMany 真的只是一个单子绑定:)。

          所以你会这样做:

          seq { for i in items1 do
                   for j in items2 do
                      yield ....  };
          

          【讨论】:

          • 我在 F# 库或 powerpack 中都没有看到 Seq.bind。你的意思是Seq.collect
          • 您看不到Seq.bind,因为它不是Seq 模块中的函数;它是seq monoid 底层工作流构建器的一种方法。
          • @pblasucci:原则上是的,但seq { .. } 不是真正的计算表达式。它由编译器专门处理,seq 并不是真正的计算构建器(它只是一个函数seq&lt;'a&gt; -&gt; seq&lt;'a&gt;。所以,Seq.collect 是正确的答案,但它并没有真正用于序列的编译形式表达。
          • 这看起来比声明性的 SelectMany 更迫切
          • @Tomas:感谢您的澄清。 seq的特殊情况处理是不是因为频繁出现,需要特别优化?
          猜你喜欢
          • 1970-01-01
          • 2019-02-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-07-04
          • 1970-01-01
          相关资源
          最近更新 更多