【问题标题】:Functional Transformation of a collection of Option<T>Option<T> 集合的功能转换
【发布时间】:2020-03-20 01:56:42
【问题描述】:

我有一个Option&lt;T&gt; 实例的集合,并希望从IEnumerable&lt;Option&lt;T&gt;&gt; 转换为Option&lt;IEnumerable&lt;T&gt;&gt;

如果 ALL 选项有一个值,那么我想要一个带有收集值的 Some&lt;IEnumerable&lt;T&gt;&gt;,如果集合中的 ANY 项目是 None,那么我想要一个None&lt;IEnumerable&lt;T&gt;&gt;

这似乎是一个很常见的函数转换,但我不确定它是否存在于主流函数库中,或者它会被称为什么。似乎类似于FlatMap,但不完全是,因为我不想只过滤掉None 值。

我可以自己实现它,但想知道它是否已经作为函数构造存在。不要介意什么语言,C#、Scala、Haskell 等。

【问题讨论】:

  • 是的,这是一个常见的转换,它被称为sequence,它具有以下签名sequence[F[_] : Traverse, G[_] : Applicative, A](fga: F[G[A]]): G[F[A]]Scala 中提供的签名C# 中好像你需要它,我不知道它有没有,但至少你已经知道要搜索什么了。
  • 感谢@LuisMiguelMejíaSuárez!很有意思。我已经完成了一些 Scala,因此将进行调查。
  • 注意,如果您在 map 之后得到了该选项集合,而不是先调用 map,然后再调用 sequence,您可以使用 traversetraverse[F[_] : Traverse, G[_] : Applicative, A, B](fa: F[A])(f: A =&gt; G[B]): G[F[B]] 它遵循以下相等 sequence(fga) === traverse(fga)(identity) 因此,您可以得出结论 sequence(map(fa)(f)) === traverse(fa)(f)
  • 我不熟悉此处标记的其他语言,但这在 Haskell 中也称为 sequence - 或 sequenceA 是适用于任何 Traversable 持有 @987654341 的最通用形式@值。
  • 酷,sequencetraverse。看起来我有一个很好的调查起点。谢谢大家!

标签: c# scala haskell functional-programming monads


【解决方案1】:

Language-ext 作者在这里,你只需要调用.Sequence() 来反转内部和外部 monad:

IEnumerable<Option<int>> items = ...;

Option<IEnumerable<int>> result = items.Sequence();

如果序列中的任何项目是None,则返回None,否则返回Some,它具有您正在寻找的确切行为。

您还可以使用.Traverse(...) 随时​​映射结果。

【讨论】:

    【解决方案2】:

    我玩了一下,似乎 f# fold 和 foldBack 在这种情况下可能有用。以下是一些示例:

    let l1 = [Some 1; Some 2; None]
    let l2 = [Some 3; Some 4; Some 5]
    
    
    let acc (ac: int list option) le = 
        match ac, le with
        | Some ac, Some le -> Some (le::ac)
        | _, _ -> None
    
    //this will reverse the order of elements if all present
    let r1 = l1 |> List.fold acc (Some [])
    let r2 = l2 |> List.fold acc (Some [])
    
    //the order of elements preserved
    let acc2 le (ac: int list option) = acc ac le
    let r3 = List.foldBack acc2 l1 (Some [])
    let r4 = List.foldBack acc2 l2 (Some [])
    
    //or...
    let r3b = l1 |> List.foldBack acc2 <| (Some [])
    let r4b = l2 |> List.foldBack acc2 <| (Some [])
    

    【讨论】:

      【解决方案3】:

      您可以推出自己的扩展方法,该方法依赖于 System.Linq 命名空间中的可用原语:

      public static Option<IEnumerable<T>> AllSome<T>(this IEnumerable<Option<T>> input)
                  where T : class
                  => (input.All(o => o.IsSome)) ?
                      new Some<IEnumerable<T>>(input.Select(o => (o as Some<T>).Value)) :
                      new None<IEnumerable<T>>() as Option<IEnumerable<T>>;
      

      这是基于我为支持此示例而编写的 Option 类型的一次性实现,请随意调整以适应您的 Option:

      public class Option<T>
          where T: class
      {
          protected T value;
          public bool IsSome => value != default(T);
      }
      
      public class Some<T> : Option<T>
          where T: class
      {
          public Some(T value) => base.value = value;
          public T Value => base.value;
      }
      
      public class None<T> : Option<T>
          where T: class
      {
      
      }
      

      【讨论】:

      • 看起来很不错 ?
      【解决方案4】:

      函数式编程概念的“语言家族”不止一个。

      在 Haskell(显然还有 Scala)中,这称为sequence,是称为Traversable 的抽象或类型类的一部分; sequence 是一种特殊的遍历。

      存在其他语言家族,最著名的是 ML。这就是术语Option 的来源(在Haskell 中称为Maybe)。虽然我精通 F#(这是一种 ML 方言),但我不知道 traverse/sequence 在那里有一个既定的术语。

      【讨论】:

        【解决方案5】:

        正如你所说,你不介意这种语言,我在这里有一个使用 F# 的示例,它具有开箱即用的 Option&lt;T&gt;

        let chooseAll source =
            let anyNone = source |> Seq.exists Option.isNone
            if anyNone then None else Some source
        
        let s = [Some 1; Some 2; None]
        let result1 = s |> chooseAll // None
        let s = [Some 4; Some 5]
        let result2 = s |> chooseAll // Some {4;5}
        

        如果您不熟悉 F#,SeqIEnumerable&lt;T&gt; 的类型别名。这里chooseAll 有签名seq&lt;'a option&gt; -&gt; seq&lt;'a&gt; option,转换为C# 友好的语法是IEnumerable&lt;Option&lt;T&gt;&gt; -&gt; Option&lt;IEnumerable&lt;T&gt;&gt;

        【讨论】:

        • 我对 F# 也很好 :) 从你的例子中,你说从 [Some 1; Some 2; None] 我最终会得到 Some {1;2},但这不是我想要的。在这个例子中,我想要一个 None,因为集合中至少有 1 个 None。
        • D'oh,非常重要的误读!你介意源被列举不止一次吗?
        • 好吧,我可以自己实现该方法而没有太多问题。我主要是想弄清楚这是否是一个标准的功能结构并且可以在核心库中使用,以及它的名称是什么。从上面的答案来看,似乎是sequencetraverse。现在只需要检查 F# 等中的等价物是否可用,如果我必须编写自己的实现,给它起什么名字。
        • 啊,我明白了。我已经更新了我的答案,但是不,我不知道这样做的标准函数,所以无法帮助命名(顺便说一下,命名很难!)
        【解决方案6】:

        这些语言结构不是最初在 C# 中呈现的;但是,有多个函数式编程库可用于此类目的。我的选择是 Paul Louth 的language-ext;迄今为止我为 C# 找到的最丰富的 FP 库。

        【讨论】:

        • 是的,我正在使用那个库!我的问题仍然存在,在语言扩展库中是否存在进行这种转换的现有命名方法?
        • 我无法确定;但我想,这些转换可以很容易地使用System.LinqSelectAllAny 来实现。
        • 它确实有Somes (louthy.github.io/language-ext/LanguageExt.Core/…),这是相反的,技术上可以用来实现你想要的,但效率很低。
        猜你喜欢
        • 1970-01-01
        • 2019-05-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-14
        • 2011-06-11
        • 2014-03-24
        相关资源
        最近更新 更多