基本上如果我在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) 100
和100 |> 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]
.