F#:配管与排版与。。。作曲?
我对一切事物都是陌生的——F#,一般的编程,还有这个社区。我是一名数学家,本科时曾短暂接触计算机科学。我试图在F#中完成一些任务,展示了三种不同的函数组合方式,但没有解释重复。这里是相关信息的链接,看看我的意思 关键字也定义命名函数。F#:配管与排版与。。。作曲?,f#,F#,我对一切事物都是陌生的——F#,一般的编程,还有这个社区。我是一名数学家,本科时曾短暂接触计算机科学。我试图在F#中完成一些任务,展示了三种不同的函数组合方式,但没有解释重复。这里是相关信息的链接,看看我的意思 关键字也定义命名函数。 let negate x = x * -1 let square x = x * x let print x = printfn "The number is: %d" x let squareNegateThenPrint x = print (nega
let negate x = x * -1
let square x = x * x
let print x = printfn "The number is: %d" x
let squareNegateThenPrint x =
print (negate (square x))
管道操作符|>
用于将函数和参数链接在一起。双回溯标识符有助于提高可读性,尤其是在单元测试中:
let ``square, negate, then print`` x =
x |> square |> negate |> print
let squareNegateThenPrint' =
square >> negate >> print
组合运算符用于组合函数:
let ``square, negate, then print`` x =
x |> square |> negate |> print
let squareNegateThenPrint' =
square >> negate >> print
通过检查和玩VS F#与功能交互:
let f x = [x]
let g y = [y]
let h1 = f >> g
let h2 x = x |> f |> g
平方反整数x
``平方,求反,然后打印“”x
平方负整数'
这似乎是一个清单,列出了三种实现完全相同的目标的方法
事情
这里有什么细微差别吗?我确信,给定相同的int
它们都将返回相同的int,但超出该值如何?我是什么
我没看见?每种方法的优点和缺点是什么
三种方法
2和3都使用“运算符”,而1似乎是组合函数的常用“数学”方式,用于从旧函数生成新函数。我怀疑选项3真的等同于1(从某种意义上说,>
运算符的定义使得square>>negate>>print
实际上被计算为print(negate(square x))
但是代码在可读性方面有好处,因为您可以按照函数名的发生顺序而不是通常的数学符号的相反顺序来查看函数名,并且这样定义可以节省一两次击键,因为您不必在函数名的末尾包含x
,因为
运算符可能会左函数自动继承对右函数变量的依赖,而不显式引用该变量
但是,管道方法是如何发挥作用的呢
运算符一个更一般的运算符,恰好为其工作
功能组合
另外,我在发布之前也用谷歌搜索了很多次,并试图阅读文档,但我没有取得任何进展。我确信,如果我继续学习这门语言,在明年的某个时候,我会理解其中的差异。但我也相信这里的人可以加快这一过程,并解释或提供一些很好的例子。洛杉矶不幸的是,我对C#或任何其他语言(数学除外)都不精通因此,对于一个完整的noob(而不仅仅是f#noob)的解释是值得赞赏的。谢谢!这些基本上都是在不同情况下使用的等效概念。没有一种正确或错误的方法可以普遍适用,但有时你可以利用已经成为一种趋势的流水线和合成操作符通过实践和更多地接触F#编程模式,这一点显而易见
举几个例子,在处理序列时经常使用管道,因为它允许非常长的操作链以一种可读的方式组合在一起,这种方式看起来有点像流畅的查询语法
[0..100]
|> List.filter (fun x -> x % 2 = 0)
|> List.map (fun x -> x / 2)
|> List.sum
对于我们中许多一直使用F的人来说,这比类似于List.sum(List.map(fun x->x/2)(List.filter(fun x->x%2=0)[0..100])这样的东西可读性好得多
在使用高阶函数(如bind
)时,通常使用合成。例如:
[0..5]
|> List.tryFind (fun x -> x = 3)
|> Option.bind ((*) 3 >> Some)
这里,我们使用管道在列表上执行tryFind
,并通过管道将选项
类型返回到选项。bind
接受签名为int->Option'b
的函数,但如果我们已经有一个函数int->int
(如乘法)我们可以使用>
将该函数与Some
组合,并将组合后的函数传递给bind
((*)3
只是将3
部分应用于乘法函数的F#语法,返回一个将任何整数乘以3的函数)
我们可以通过编写Option.bind(funx->Some(x*3))
来实现同样的效果,但是>
操作符和部分应用程序语法为我们节省了一些击键时间。首先-是的,所有这些方法在“逻辑上”都是等效的当编译到硬件时。这是因为|>
和>
运算符被定义为内联
。定义大致如下:
let inline (|>) x f = f x
let inline (>>) f g = fun x -> g (f x)
inline
关键字的含义是,编译器将用函数体替换对函数的调用,然后编译结果。因此,以下两项:
x |> f |> g
(f >> g) x
g (f x)
let b = "abcd" |> (fun x -> x.Length)
let a = (fun x -> x.Length) "abcd"
将以与以下完全相同的方式进行编译:
x |> f |> g
(f >> g) x
g (f x)
let b = "abcd" |> (fun x -> x.Length)
let a = (fun x -> x.Length) "abcd"
然而,在实践中也存在一些问题。
let negate x = x * -1
let square x = x * x
let print x = printfn "The number is: %d" x
let squareNegateThenPrint x =
print (negate (square x))
一个是类型推断和它与类/接口的相互作用。考虑以下内容:
x |> f |> g
(f >> g) x
g (f x)
let b = "abcd" |> (fun x -> x.Length)
let a = (fun x -> x.Length) "abcd"
尽管这些定义在逻辑上和编译后的形式上都是等价的,但第一个定义将编译,第二个定义将不会编译。这是因为F#中的类型推理从左到右进行,没有双回退,因此,在第一个定义中,当编译器到达x.Length
时,它已经知道了x
是一个字符串,因此它可以正确解析成员查找。在第二个示例中,编译器不知道x
是什么,因为它还没有遇到参数“abcd”
另一个问题与恐惧有关。简单来说,它说一个语法上(而非逻辑上!)的定义是一个值(相对于