我从 F# 开始,在理解语法方面取得了一些进展。但是,我仍然不清楚使用 F# 功能的最佳方式。在我来自的 Python 中,通常有一种“最好的”(几乎是规范的)做事方式。也许F#也是如此,但我还没有弄清楚。所以我下面的问题是关于使用 F# 的最佳方式,而不是关于 F# 语法的技术问题。
最近我看了 Eric Meijer 博士的视频 (C9 Lectures - Functional Programming Fundamentals Chapter 2 of 13)
Meijer 博士在其中赞扬了 OOP 的点表示法,观察到它允许 Intellisense 显示可用方法的列表。他感叹这样的功能在纯 FP 中是不可用的,这通过帮助程序员“前进”而使编程变得如此容易。
一些实验表明,Intellisense 当然适用于 F# 类,但也适用于 F# 记录,与类一样,它使用点表示法。这意味着一个人可以塑造自己的代码,以便利用 Intellisense 而无需一路编写类(我假设在 F# 中类比记录更重且更慢,如果我错了,请纠正我)。
以下代码显示了两种编写执行相同操作的代码(称为“版本”)的方法:
// Create a record type with two values that are functions of two arguments
type AddSub = {add2: int -> int -> int; sub2: int -> int -> int}
// Instantiate a record
let addsub a =
{add2 = (fun x y -> a + x + y); sub2 = (fun x y -> a - x - y)}
// Returns 7, Intellisense works on (addsub 0).
(addsub 0).add2 3 4
// Returns 3, Intellisense works on (addsub 10).
(addsub 10).sub2 3 4
// Create two functions of three arguments
let add3 a x y = a + x + y
let sub3 a x y = a - x - y
// Also got 7, no Intellisense facility here
add3 0 3 4
// Also got 3, no Intellisense facility here
sub3 10 3 4
这表明在纯 FP 和 OOP 之间存在一种中间策略:使用函数值创建记录类型,如上。这种策略将我的代码组织在以对象(记录实例)为中心的有意义的单元中,并允许我使用 Intellisense,但缺少类提供的一些特性,如继承和子类多态性(如果我在这里错了,请再次纠正我)。
来自 OOP 背景,我觉得如果像
a
这样的对象在上面的代码中,在某种程度上比参数 x 和 y 更“重要”(我将保留该术语未定义),这样的编码策略是合理的,无论是基于代码组织还是使用 Intellisense 的能力。另一方面,由于 OOP 的复杂性,我宁愿留在“纯粹的”FP 领域。记录的使用是否是两种极端选择(OOP 和纯 FP)之间值得折衷的选择?
一般来说,给定三种替代方案(纯 FP、上述记录或类),在哪些情况下一种替代方案优于其他方案的一般准则是什么?
最后,是否有其他可用的编码策略可以帮助我组织代码和/或利用 Intellisense?
最佳答案
Intellisense 在 F# 中仍然可以正常工作,但在模块级别而不是在类级别。即,我刚刚输入 List.
一旦我输入了这个点,VS Code(使用提供 F# Intellisense 的 Ionide 插件)给了我一个可能的完成列表:append
, average
, averageBy
, choose
, chunkBySize
...
要从您自己的函数中获得好处,请将它们放在一个模块中:
module AddSub =
let add2 x y = x + y
let sub2 x y = x - y
let add3 a x y = a + x + y
let sub3 a x y = a - x - y
现在当你输入
AddSub.
, 输入点后,Intellisense 会提示 add2
, add3
, sub2
, 和 sub3
尽可能跟进。然而,您已经以“适当的”F# 风格保持了函数“干净”和可 curry 。最后,关于功能设计的另一条建议。您提到有一个参数(如
a
和 add3
函数中的 sub3
)在某种程度上比其他参数更“重要”的函数。在 F# 中,任何此类参数都应该是最后一个参数,因为这允许您使用 |>
将其放入函数链中。运算符,如下所示:let a = 20
a |> AddSub.add3 5 10 |> AddSub.sub3 2 3 // Result: 30
或者更确切地说,当存在从单个起始值开始的操作“管道”时,使用大多数人喜欢的样式:
let a = 20
a
|> AddSub.add3 5 10
|> AddSub.sub3 2 3
// Result: 30
当管道中有更多操作时,垂直排列管道变得更加重要。我的经验法则是,如果在管道中指定了两个以上的“额外”参数(上面的管道有四个“额外”参数,
add3
和 sub3
函数各有两个),或者如果任何一个“额外”参数比单个值更复杂(例如,如果一个参数是匿名函数,如 (fun x -> sprintf "The value of x was %d" x)
或类似的),那么您应该垂直排列它。附言如果您还没有阅读,请阅读 Scott Wlaschin 的优秀系列文章 Thinking Functionally .这将有助于解释有关此答案的许多事情,例如为什么我建议将“最重要”的论点放在最后。如果您没有立即理解我关于如何将它与
|>
一起使用的简短评论。参数,或者如果您对此答案有任何其他困惑,那么您可能会从 Scott 的文章中受益匪浅。
关于oop - 我应该编写利用 Intellisense 的代码吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42469093/