Sol*_*lma 3 oop intellisense f# functional-programming purely-functional
我从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
Run Code Online (Sandbox Code Playgroud)
这表明纯FP和OOP之间存在中间策略:创建具有函数值的记录类型,如上所述.这样的策略将我的代码组织在以对象(记录实例)为中心的有意义的单元中,并允许我使用Intellisense,但缺少类提供的一些功能,如继承和子类多态(如果我在这里错了,再次纠正我).
来自OOP背景我觉得如果像a上面的代码中的对象在某种程度上更"重要"(我将保留该术语未定义)而不是参数x和y这样的编码策略将是合理的,两者都基于代码组织和使用Intellisense的能力.另一方面,由于OOP的复杂性而被烧毁,我宁愿留在"纯粹"的FP领域.
在两种极端替代方案(OOP和纯FP)之间使用记录是否值得妥协?
一般而言,考虑到三种替代方案(纯粹的FP,上述记录或类别),对于一种替代方案优先于其他方案的情况,一般指导方针是什么?
最后,是否有其他编码策略可以帮助我组织我的代码和/或利用Intellisense?
Intellisense在F#中仍然可以正常工作,但是在模块级别而不是在类级别.也就是说,我刚才输入List.,一旦我输入点,VS代码(与Ionide插件提供F#的智能感知)给了我尽可能完整的列表: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
Run Code Online (Sandbox Code Playgroud)
现在,当你键入AddSub.,您键入的点之后,智能感知会建议add2,add3,sub2,并sub3尽可能followups.然而,你以"适当的"F#风格保持你的功能"干净"和可爱.
最后,关于功能设计的另一条建议.你提及具有功能,其中一个参数(如a在add3和sub3功能)以某种方式比其它参数更"显著".在F#中,任何此类参数都应该是最后一个参数,因为这允许您使用|>运算符将其放在函数链中,如下所示:
let a = 20
a |> AddSub.add3 5 10 |> AddSub.sub3 2 3 // Result: 30
Run Code Online (Sandbox Code Playgroud)
或者更确切地说,当使用单个起始值的"管道"操作时,使用大多数人喜欢的样式:
let a = 20
a
|> AddSub.add3 5 10
|> AddSub.sub3 2 3
// Result: 30
Run Code Online (Sandbox Code Playgroud)
当有更多的操作时,垂直排列管道变得更加重要.我的经验法则是,如果在管道中指定了多于两个"额外"参数(上面的管道有四个"额外"参数,两个用于add3和sub3函数),或者如果任何"额外"参数是比单个值更复杂(例如,如果一个参数是匿名函数(fun x -> sprintf "The value of x was %d" x)或类似的某些函数),那么您应该垂直排列它.
PS如果您尚未阅读,请阅读Scott Wlaschin关于思考功能的精彩系列.它将有助于解释有关此答案的许多内容,例如为什么我建议最后提出"最重要"的论点.如果你没有立即理解我的简短评论如何使你能够将它与|>参数一起使用,或者如果还有其他任何令你困惑的答案,那么你可能会从Scott的文章中获得很多好处.