【问题标题】:What is the relation between C# extension methods and the F# pipe-forward operator?C# 扩展方法和 F# 管道转发运算符之间有什么关系?
【发布时间】:2011-03-15 11:49:24
【问题描述】:

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

例如,给定一个名为 int 的 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# 扩展方法中的第一个参数 - 但除此之外,在描述扩展方法或向其他开发人员传递管道时使用该解释是否有任何问题?

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

【问题讨论】:

    标签: c# f# extension-methods


    【解决方案1】:

    我不会做这个类比,因为扩展方法可以像实例方法一样被调用,而管道显然在语法上完全不同。此外,F# 也有扩展方法(以及扩展属性和事件!),所以我认为确实有可能引起混淆。

    但是,我认为管道样式和扩展方法都允许将计算流畅地描述为一组步骤,这可能是您所驾驶的相似之处。从概念上讲,最好让代码反映“取这组整数,只保留满足 i>0 的子集,然后对它们中的每一个求平方”的想法,这就是用英语描述任务的方式。管道语法和 C# 扩展方法都允许这种事情。

    【讨论】:

    • 感谢您的回复 - 我确实同意造成混淆的真正可能性,并且您在扩展属性等方面提出了很好的观点。我不确定我是否同意管道在语法上完全不同。 F# 示例中的 |> 看起来好像与 .在 C# 示例中? C# 中的扩展方法可以像实例方法一样调用,但如果您可以在 F# 中编写 let (.) arg func = func arg 并使用 .作为管道转发,看起来您正在调用 F# 中的实例方法,但您不是 - 而且您也不在 C# 中。
    【解决方案2】:

    我也认为这是一个非常有用的类比。事实上,在我的Real World Functional Programming 书中(试图向具有 C# 背景的人解释函数式思想)中描述流水线操作符时,我正是使用了这个类比。以下是第 6 章的引述。

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

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

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


    流水线操作符 (|>) 允许我们在左侧编写函数的第一个参数;也就是说,在函数名本身之前。如果我们想按顺序对某个值调用多个处理函数并且我们想写入首先被处理的值,这很有用。让我们看一个例子,展示如何在 F# 中反转一个列表,然后获取它的第一个元素:

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

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

    list.Reverse().Head();
    

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

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

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

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

    【讨论】:

    • 关于你最后的观点,我相信 F# 确实为 C# 风格和 F# 风格的扩展成员提供了对 Intellisense 的编辑器支持。
    • @kvb:是的,没错。我正在考虑对流水线操作员的类似支持 - 当您键入例如nums |> 它可以自动显示以list<int> 作为最后一个参数的函数......(编辑后的帖子更清楚)
    • 我非常同意您的观点,即与 C# 中的扩展方法相关的“向对象添加成员”功能非常概念化。当我们实现扩展方法时,我们基本上编写了一个静态方法,我们可以像任何其他静态方法一样调用它。但是,当我们将关键字“this”添加到第一个参数时,会发生一些神奇的事情,我们现在可以通过第一个参数的实例调用静态方法。我认为“向对象添加成员”不是考虑扩展方法的好方法,并且可能导致它们被滥用(例如 Object.ToInt32())。
    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-08-19
      • 1970-01-01
      • 2010-10-15
      • 2015-09-21
      • 2017-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多