C# 扩展方法和 F# 管道转发运算符之间有什么关系?

发布于 2024-09-09 15:55:45 字数 532 浏览 8 评论 0原文

在使用 F# 解决了一些小问题之后,我发现将 C# 扩展方法视为“将 .进入管道转发运算符'。

例如,给定名为 ints 的 Int32 序列,C# 代码:

ints.Where(i => i > 0)
    .Select(i => i * i)

类似于 F# 代码

let where = Seq.filter
let select = Seq.map

ints |> where (fun i -> i > 0)
     |> select (fun i -> i * i)

事实上,我经常将 IEnumerable 上的扩展方法视为简单的函数库,提供与 F# 的 Seq 模块类似的功能。

显然,管道参数是 F# 函数中的最后一个参数,但却是 C# 扩展方法中的第一个参数 - 但除此之外,在描述扩展方法或通过管道转发给其他开发人员时使用该解释是否存在任何问题?

我会误导他们吗?或者这是一个有用的类比?

After using F# for a few small problems, I've found it helpful for myself to think of C# extension methods as 'a way of turning the . into a pipe-forward operator'.

For example, given a sequence of Int32s named ints, the C# code:

ints.Where(i => i > 0)
    .Select(i => i * i)

is similar to the F# code

let where = Seq.filter
let select = Seq.map

ints |> where (fun i -> i > 0)
     |> select (fun i -> i * i)

In fact, I often think of the extension methods on IEnumerable as simply a library of functions that provide similar functionality to F#'s Seq module.

Obviously the piped parameter is the last parameter in an F# function, but the first parameter in a C# extension method - but apart from that, are there any issues with using that explanation when describing extension methods or pipe-forward to other developers?

Would I be misleading them, or is it a helpful analogy?

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

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

发布评论

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

评论(3

守望孤独 2024-09-16 15:55:45

我不会做这样的类比,因为扩展方法可以像实例方法一样被调用,而管道在语法上显然有很大不同。此外,F# 还具有扩展方法(以及扩展属性和事件!),因此我认为确实有可能造成混乱。

然而,我认为管道样式和扩展方法确实都允许将计算流畅地描述为一组步骤,这可能就是您所追求的相似之处。从概念上讲,最好让代码反映出“采用这组整数,仅保留满足 i>0 的子集,然后对每个子集进行平方”的想法,这就是用英语描述任务的方式。管道语法和 C# 扩展方法都允许此类操作。

I wouldn't make that analogy because an extension method can be called as if it were an instance method whereas piping is clearly syntactically quite different. Additionally, F# also has extension methods (as well as extension properties and events!), so I think there's a real possibility of causing confusion.

However, I think that it is true that both the piping style and extension methods allow computations to be described fluently as a set of steps, which is probably the similarity that you are driving at. Conceptually, it is nice have the code reflect the idea of "take this set of ints, keep only the subset satisfying i>0, and then square each of those", which is how the task might be described in English. Both the piping syntax and C# extension methods allow this sort of thing.

但可醉心 2024-09-16 15:55:45

我也认为这是一个非常有用的类比。事实上,我在《现实世界函数式编程》一书中描述管道操作符时正是使用了这个类比(它试图向具有 C# 背景的人解释函数式思想)。下面是第六章的引用。

关于两者之间的差异 - 有一些概念上的差异(例如扩展方法“向对象添加成员”),但我认为这对我们在实践中使用它们的方式没有任何影响。

  • C# 扩展方法和 F# 函数之间一个显着的实际区别是编辑器支持 - 当您键入“.”时。在 C# 中,您可以看到特定类型的扩展方法。我相信,当您键入 |> 时(原则上),F# IntelliSense 也可以显示过滤列表,但这可能需要更多工作,而且尚不支持。

  • 这两个构造都用于启用基于表达式的组合编程风格。我的意思是,您可以将更大的代码部分编写为描述应该做什么的单个表达式(如果没有扩展方法/管道操作符,您可能会将代码分解为多个词干)。我认为这种编程风格通常会产生更具声明性的代码。


管道操作符(|>)允许我们将函数的第一个参数写在左侧;也就是说,在函数名称本身之前。如果我们想要按顺序对某个值调用多个处理函数并且想要首先写入正在处理的值,那么这非常有用。让我们看一个示例,展示如何在 F# 中反转列表,然后获取其第一个元素:

List.hd(List.rev [1 .. 5])

这不是很优雅,因为操作的编写顺序与执行顺序相反,并且正在处理的值位于右侧,由几个大括号包围。在 C# 中使用扩展方法,我们可以这样写:

list.Reverse().Head();

在 F# 中,我们可以使用流水线运算符得到相同的结果:

<前><代码>[1 .. 5] |>列表.rev |>列表.hd

尽管这可能看起来很棘手,但运算符实际上非常简单。它有两个参数 - 第二个参数(在右侧)是一个函数,第一个参数(在左侧)是一个值。该运算符将值作为参数提供给函数并返回结果。

在某种意义上,管道类似于在对象上使用点表示法调用方法,但它不限于对象的内部方法。这与扩展方法类似,因此当我们编写通常与管道操作符一起使用的 F# 函数的 C# 替代方案时,我们会将其实现为扩展方法。

I also think that this is a very useful analogy. In fact, I used exactly this analogy when describing the pipelining operator in my Real World Functional Programming book (which tries to explain functional ideas to people with C# background). Below is a quote from Chapter 6.

Regarding the differences between the two - there are some conceptual differences (e.g. extension methods "add members to objects"), but I don't think this has any impact on the way we use them in practice.

  • One notable practical difference between C# extension methods and F# functions is editor support - when you type "." in C#, you can see extension methods for the particular type. I believe that F# IntelliSense could show a filtered list when you type |> (in principle) as well, but it is probably much more work and it isn't supported yet.

  • Both of the constructs are used to enable expression-based compositional programming style. By this I mean that you can write much larger portions of code as a single expression that describes what should be done (without extension methods/pipelining operator, you would probably break code into multiple stements). I think that this style of programming generally leads to a more declarative code.


The pipelining operator (|>) allows us to write the first argument for a function on the left side; that is, before the function name itself. This is useful if we want to invoke a several processing functions on some value in sequence and we want to write the value that's being processed first. Let's look at an example, showing how to reverse a list in F# and then take its first element:

List.hd(List.rev [1 .. 5])

This isn't very elegant, because the operations are written in opposite order then in which they are performed and the value that is being processed is on the right side, surrounded by several braces. Using extension methods in C#, we'd write:

list.Reverse().Head();

In F#, we can get the same result by using the pipelining operator:

[1 .. 5] |> List.rev |> List.hd

Even though, this may look tricky, the operator is in fact very simple. It has two arguments - the second one (on the right side) is a function and the first one (on the left side) is a value. The operator gives the value as an argument to the function and returns the result.

In some senses, pipelining is similar to calling methods using dot-notation on an object, but it isn't limited to intrinsic methods of an object. This is similar to extension methods, so when we write a C# alternative of an F# function that's usually used with the pipelining operator, we'll implement it as an extension method.

乄_柒ぐ汐 2024-09-16 15:55:45

如果您希望重用这些管道,您可能还需要查看反向函数组合运算符。

let where = Seq.filter
let select = Seq.map
let whereGreaterThanOneComputeSquare = 
   where (fun i -> i > 0) << select (fun i -> i * i)
ints |> whereGreaterThanOneComputeSquare

You may also want to look at the reverse function composition operator if you wish to reuse these pipelines.

let where = Seq.filter
let select = Seq.map
let whereGreaterThanOneComputeSquare = 
   where (fun i -> i > 0) << select (fun i -> i * i)
ints |> whereGreaterThanOneComputeSquare
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文