如何为在列表上执行的一组操作正确地链接f#函数



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

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

我想排序降序,然后取3。我可以做这个链接:

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

这工作得很好,将为我想要的工作,当使用f#交互式调试器。但我试图做它内联在所有一个表达式和没有得到正确的语法。这是可能的,我只是不明白语法。

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();

我只是期待你可以像流畅的内联语法链继续,但似乎我缺少语法或没有得到一些简单的东西。我最近才开始学习f#,但到目前为止我很喜欢它。我只是一时想不明白事情是怎么联系在一起的。

>>运算符取两个函数并生成一个函数,该函数是这两个函数的复合。

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

假设我们有

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))

我想你会感到困惑,因为你错过了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组合一个函数List.sortDescending。这肯定会因为出现类型错误而失败。您可能想要做的只是用单个参数部分应用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

当然你也可以在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 @"..packagesFSharp.Core.Fluent-4.0.1.0.0.5libportable-net45+netcore45+wpa81+wp8+MonoAndroid1+MonoTouch1FSharp.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] .

最新更新