在使用F#解决一些小问题之后,我发现将C#扩展方法看作是一种转向的方法对我自己很有帮助。进入管道运营商'。
例如,给定一个名为int的Int32s序列,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#扩展方法中的第一个参数 - 但除此之外,在描述扩展方法或管道转发给其他开发人员时是否存在使用该解释的任何问题?
我会误导他们,还是一个有用的类比?
答案 0 :(得分:6)
我不会这样做,因为可以调用扩展方法,就好像它是一个实例方法,而管道在语法上显然是完全不同的。另外,F#还有扩展方法(以及扩展属性和事件!),所以我认为真的有可能引起混淆。
但是,我认为管道样式和扩展方法都可以将计算流程描述为一组步骤,这可能与您所驾驶的相似。从概念上讲,如果代码反映了“采用这组内容,只保留满足i> 0的子集,然后对每个那些进行平方”的想法很好,这就是用英语描述任务的方式。管道语法和C#扩展方法都允许这样的事情。
答案 1 :(得分:3)
我也认为这是一个非常有用的比喻。实际上,在我的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#替代时,我们将其实现为扩展方法。
答案 2 :(得分:0)
如果您希望重用这些管道,您可能还需要查看反向函数组合运算符。
let where = Seq.filter
let select = Seq.map
let whereGreaterThanOneComputeSquare =
where (fun i -> i > 0) << select (fun i -> i * i)
ints |> whereGreaterThanOneComputeSquare