F# 产量!运算符 - 实现和可能的 C# 等效项

发布于 2024-09-14 15:27:46 字数 1053 浏览 6 评论 0原文

我目前正在学习 F#,我真的很喜欢 yield! (yield-bang) 运算符。不仅是因为它的名字,当然也因为它的作用。

yield! 运算符基本上允许您从序列表达式中生成序列的所有元素。这对于编写枚举器很有用。由于我经常遇到大型、复杂的枚举器,因此我对可以用来分解它们并由更简单的枚举器组合它们的策略感兴趣。

不幸的是,yield! 运算符在 C# 中不可用。据我了解,它的作用类似于 foreach (var x in source) Yield x; 但我正在读的书 (Petricek 的真实世界 F# - Manning) 表明它具有更好的性能...

  • 那么 F# 编译器在这里到底做了什么? (是的,我也可以使用 Reflector 查看它,但我想更详细地描述该机制)。

为了在 C# 中实现类似的构造,我探索了多种方法,但没有一种方法像 yield! 运算符那样简洁,而且我也不确定它们的复杂性。如果我的 BigO 号码正确,有人可以提供意见吗?

  • 将枚举数分解为多个私有枚举数,然后从公共枚举数中生成每个元素:

    foreach(第 1 部分()中的 var x)yield x
    foreach (part2() 中的 var x) 产生 x
    

    这将有效地导致每个元素的“双倍收益”。那是 O(2n) 吗? (或者可能更糟?)无论如何,使用这种方法会阻止我从任何子部分使用 yield break;

  • 将枚举数分解为多个私有枚举数,然后将公共枚举数中的所有私有枚举数连接起来:

    返回part1().Concat(part2())
    

    我相信这与上述解决方案没有什么不同,因为 Concat() 是按照我上面概述的方式实现的。

还有其他选择吗?

I'm currently learning F# and I really love the yield! (yield-bang) operator. Not only for its name but also for what it does of course.

The yield! operator basically allows you to yield all elements of a sequence from a sequence expression. This is useful for composing enumerators. Since I regularly encounter big, complicated enumerators I am interested in strategies we can use to break them up and compose them from simpler enumerators.

Unfortunatetely, the yield! operator is not available in C#. As far as I understand, what it does is like a foreach (var x in source) yield x; but the book I'm reading (Petricek's Real World F# - Manning) suggests that it has better performance...

  • So what exactly does the F# compiler do here? (Yes, I can look at it using Reflector too but I'd like to have a more detailed description of the mechanism).

In order to achieve a similar construct in C# I have explored multiple ways, but none of them is as concise as the yield! operator and I'm also not sure about the complexity of them. Could someone please provide input if my BigO numbers are correct?

  • Decompose enumerator into multiple private enumerators and then yield each element from the public enumerator:

    foreach (var x in part1()) yield x
    foreach (var x in part2()) yield x
    

    This will effectively result in a "double yield" on each element. Is that O(2n) then? (or possibly worse?) Anyway, using this approach stops me from using yield break; from any of my subparts.

  • Decompose enumerator into multiple private enumerators and then concat all private enumerators from the public enumerator:

    return part1().Concat(part2())
    

    I believe this is no different from the aforementioned solution because Concat() is implemented the way I outlined above.

Any other options?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

不离久伴 2024-09-21 15:27:46

在当前版本的 C# 中,我认为除了 foreach...yield returnConcat 之外,您没有其他选择。我同意在 C# 中使用 yield! 运算符会很好,它会使某些构造更加优雅,但我怀疑此功能是否会进入“必备”列表,因为没有它我们也能轻松做到。

您可能对这篇MS 研究论文感兴趣,其中介绍了新的 yield foreach 构造:

IEnumerable<XmlNode> Traverse(XmlNode n)
{
    yield return n;
    foreach (XmlNode c in n.ChildNodes)
        yield foreach Traverse(c);
}

关于您关于复杂性的问题:在这两种情况下都是O(n)。未使用 O(2n),因为它表示与 O(n) (线性)相同的复杂度。我不认为使用当前的 C# 功能可以做得更好......

In the current version of C#, I don't think you have other options than foreach... yield return and Concat. I agree it would be nice to have the yield! operator in C#, it would make certain constructs much more elegant, but I doubt this feature will ever make it to the "must-have" list, since we can easily do without it.

You might be interested in this MS research paper, which introduces a new yield foreach construct :

IEnumerable<XmlNode> Traverse(XmlNode n)
{
    yield return n;
    foreach (XmlNode c in n.ChildNodes)
        yield foreach Traverse(c);
}

Regarding your question about complexity: in both cases it's O(n). O(2n) is not used, because it denotes the same complexity as O(n) (linear). I don't think you can do better than that with the current C# features...

孤寂小茶 2024-09-21 15:27:46

关于编译器如何转换 yield! 操作, Thomas Levesque 在其回答中引用的论文在第 4.3 节中说明了一种实现技术(特别是,他们跨越图 7-9 的示例说明了总体策略)。我认为没有任何好的方法可以从 C# 中的迭代器块中执行此操作 - 据我了解您提出的解决方案,它们在递归使用时都可能导致二次行为。您始终可以手动创建一个 NestedEnumerable 子类来实现性能优势,但这与使用普通迭代器块相比会非常难看。

Regarding how the compiler translates the yield! operation, the paper cited by Thomas Levesque in his answer illustrates one implementation technique in section 4.3 (in particular, their example spanning figures 7-9 is illustrative of the general strategy). I don't think that there's any good way to do this from within an iterator block in C# - as I understand your proposed solutions, they could both result in quadratic behavior when used recursively. You could always manually create a NestedEnumerable<T> subclass to achieve the performance benefits, but this will be quite ugly compared to using a normal iterator block.

潦草背影 2024-09-21 15:27:46

C# 中没有与 yield! 直接对应的东西。您目前陷入了 foreachyield return 的组合。

然而,IIRC、LINQ 提供了类似的功能,即 SelectMany 查询运算符,它在 C# 中转换为多个 from .. in .. 子句。

(我希望我没有混淆两个不同的概念,但是 IIRC,yield!SelectMany 本质上都是“扁平化”投影;即对象的层次结构被“扁平化”到一个列表中。)

There is no direct counterpart to yield! in C#. You're currently stuck with a combination of foreach and yield return.

However, IIRC, LINQ offers something similar, namely the SelectMany query operator, which translates to C# as multiple from .. in .. clauses.

(I'm hoping I'm not mixing up two different concepts, but IIRC, both yield! and SelectMany are essentially "flattening" projections; ie. a hierarchy of objects is "flattened" into a list.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文