如何在列表中执行的一组操作中正确链接F#函数?

时间:2016-10-13 18:54:36

标签: .net f#

所以基本上如果我在F#中有一个简单的列表

let ls = [3; 9; 2; 15; 3]

我想sortDescending然后拿3.我可以做这个链接:

let orderList ls = List.sortDescending ls
let take3 ls = List.take(3) ls
let main ls = (orderList >> take3) ls

这很好用,在使用F#Interactive调试器时可以满足我的需要。但我试图在所有一个表达式中内联,而不是正确的语法。是否可能,我只是没有得到语法。

let main2 ls = List.sortDescending >> List.take(3) ls

我正在从更多的面向对象心态转向C#我可以做到这一点:

var ls = new List<int>{ 3, 9, 2, 15, 3 };
var subset = ls.OrderByDescending(x => x).Take(3).ToList();

我只是期待你可以像流畅的语法INLINE一样继续链接,但看起来我缺少语法或者没有简单的东西。我最近才学习F#,但到目前为止我都在挖掘它。我只是很难将某些东西链接在一起。

3 个答案:

答案 0 :(得分:15)

>>运算符接受两个函数并生成一个函数,该函数是这两个函数的组合。

|>运算符接受一个值和一个函数,并将值传递给函数,生成另一个值。

假设我们有

let addone x = x + 1
let double x = x * 2

然后(addone >> double) 100100 |> addone |> double都是202.你明白为什么吗?确保清楚。

像这样想想他们。 f >> g是组成:

let compose f g =
  fun x -> g (f x)

所以

(addone >> double) 100

相同
(fun x -> double ( addone x ) ) 100

相同
double (addone 100)

x |> f是管道:

let pipe x f = f x

所以

100 |> addone |> double

相同
(addone 100) |> double

相同
double (addone 100)

在C#中,将扩展方法链接在一起的业务基本上就是管道运算符:

customers.Take(100).ToList()

相同
Enumerable.ToList(Enumerable.Take(customers, 100))

答案 1 :(得分:2)

我觉得你很困惑,因为你错过了F#中关于括号的一些细节。你写了这个:

let main2 ls = List.sortDescending >> List.take(3) ls

我认为你可能认为List.take只用一个参数调用,但事实并非如此。请注意,在F#括号中只能组合事物。它们不需要调用函数。在您的情况下,您的括号实际上没有任何效果。看看那两个函数调用。它们具有相同的含义。

List.take (3) (ls)
List.take 3 ls

如果这让你感到困惑,那就想想简单的数学。这里的括号只是组合的东西。这意味着,首先计算括号内的任何内容。

(3 * 6) + 10
3 * 6 + 10

同样在上述情况下,您可以得到相同的结果,有或没有括号。如果需要其他优先级,则只需要括号。就像首先添加6 + 10然后将结果乘以3一样。

3 * (6 + 10)

在F#中它是。首先计算括号内的内容。在上面的示例中,您只需将3放入其中。这完全没有意义。这就像你写的那样。

(3) * (6) + (10)

另见一个例子:

sqrt(2.0) + 2.0
sqrt 2.0 + 2.0

这两个例子的意思相同。这可能是你的困惑。但是用括号解释它是如何被解释的正确方法是这样写的:

(sqrt 2.0) + 2.0

围绕(2.0)的第一个括号没有意义。但是,如果您首先要添加数字,并且想要平方根4.0,则需要编写:

sqrt (2.0 + 2.0)

回到你的例子。如果我们删除3周围的括号并将必要的括号添加回代码中,如何解释代码,实际上你写的是这样的:

let main2 ls = List.sortDescending >> List.take(3) ls
let main2 ls = List.sortDescending >> (List.take 3 ls)

这意味着。您的代码首先执行List.take 3 ls。结果是List。然后,您尝试使用List.sortDescending撰写函数List。这肯定会因类型错误而失败。您可以想要做的只是将List.take部分应用于一个参数,并使用List.sortDescending组合生成的函数。在这种情况下,您需要写:

(List.take 3)

或者作为一个完整的例子:

let main2 ls = (List.sortDescending >> (List.take 3)) ls
let main2 ls = (List.sortDescending >> List.take 3) ls

上面两行中的两行再次具有相同的含义。在您的情况下,您现在所做的是创建部分应用函数List.take 3,然后使用List.sortDescending进行组合。然后返回一个新函数,然后将ls作为参数传递。请注意,此代码有点冗长。你也“理论上”可以把它写成:

let main2 ls = (List.sortDescending >> List.take 3) ls
let main2 = List.sortDescending >> List.take 3

请注意,如果不使用具体值调用函数,则可能会产生“值限制”错误。顺便说一句,还有其他方法可以编写你的功能。消除值限制错误的一种方法是:

let main ls = ls |> List.sortDescending |> List.take 3

这与:

相同
let main ls = List.take 3 (List.sortDescending ls)

顺便说一下。作为另一个与您的问题没有直接关系的通知。您还应该查看List.truncate而不是List.take。如果List.take无法返回您指定的元素的确切数量,则会引发错误。 List.truncate永远不会抛出错误。

List.take     4 [1;2;3] // throws an exception
List.truncate 4 [1;2;3] // returns -> [1;2;3]

如果您想了解有关管道,嵌套或组合的更多详细信息。我有一篇博客文章介绍了这些细节:http://sidburn.github.io/blog/2016/09/25/function-application-and-composition

答案 2 :(得分:1)

您当然可以在F#中使用linq扩展方法。

open System
open System.Linq


let ls = [3; 9; 2; 15; 3]
ls.OrderByDescending(fun x -> x).Take(3).ToList()

此外,如果您喜欢这种语法,Fsharp.Core.Fluent可以这样做:

#r @"..\packages\FSharp.Core.Fluent-4.0.1.0.0.5\lib\portable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1\FSharp.Core.Fluent-4.0.dll"
#r "System.Runtime"
open FSharp.Core.Fluent
open System.Runtime

ls.sortDescending().Take(3).toList()

请注意.ToList()之间的差异,这是.NET的通用列表: Collections.Generic.List<int> = seq [15; 9; 3].toList(),这是F#的链接列表:val it : int list = [15; 9; 3]