我应该编写利用Intellisense的代码吗?

时间:2017-02-26 13:28:35

标签: oop f# functional-programming intellisense purely-functional

我从F#开始,在理解语法方面取得了一些进展。但是,我仍然不清楚使用F#功能的最佳方法。在Python中,我来自哪里,通常有一个"最好的" (几乎是规范的)做事方式。也许F#的情况也是如此,但我还没弄明白。所以下面的问题是关于使用F#的最佳方式,而不是关于F#语法的技术问题。

最近我看到了Eric Meijer博士(C9 Lectures - Functional Programming Fundamentals Chapter 2 of 13)的视频,其中Meijer博士称赞OOP的点符号,观察它允许Intellisense显示可用方法列表。他感叹这样的设施在纯FP中是不可用的,这使得编程变得更加容易,这有助于程序员和#34;前进。"

一些实验表明,当然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?

2 个答案:

答案 0 :(得分:8)

Intellisense在F#中仍然可以正常工作,但是在模块级别而不是在类级别。即,我只输入List.,输入点后,VS Code(带有F#Intellisense的Ionide插件)给了我一个可能的完成列表:appendaverage,{ {1}},averageBychoose ...

要从您自己的功能中获益,请将它们放入模块中:

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.add2add3sub2作为后续跟踪。然而,你以“适当的”F#风格保持你的功能“干净”和可爱。

最后,关于功能设计的另一条建议。您提到了一个函数,其中一个参数(如sub3a函数中的add3)在某种程度上比其他参数更“重要”。在F#中,任何此类参数都应该是 last 参数,因为这允许您使用sub3运算符将其放在函数链中,如下所示:

|>

或者更确切地说,当一个起始值的操作“管道”时,使用大多数人喜欢的风格:

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或某些参数),那么您应该垂直排列。

P.S。如果您尚未阅读,请阅读Scott Wlaschin关于Thinking Functionally的精彩系列。它将有助于解释有关此答案的许多内容,例如为什么我建议最后提出“最重要”的论点。如果你没有立即理解我关于如何使用(fun x -> sprintf "The value of x was %d" x)参数的简短评论,或者如果有任何其他令你困惑的答案,那么你可能会获得很多好处斯科特的文章。

答案 1 :(得分:3)

你的问题相当广泛,模块的某些特定方面已经被@munn所涵盖。我想在一个非常大的F#代码库中添加一些由我自己的工作引起的想法,并且不断变化的开发团队。

代码可发现性规则。随着代码库的增长,能够看到(通过Intellisense)对象可用的方法变得越来越重要。但也许更重要的是,它可以帮助新的加入者加入你的团队,他们可能还不知道有模块X有所有方法可以处理类/记录/等实例。

我发现F# component design guide非常有帮助。它提供了很多关于如何在OOP和功能之间取得平衡的细节。有关您的具体问题,请参阅section on intrinsic operations,其中直接引用您提出的观点:

  

对属性内在的操作使用属性和方法。   这是特别要求的,因为来自函数编程背景的一些人避免一起使用面向对象的编程,更喜欢包含一组函数的模块,这些函数定义与类型相关的内部函数(例如,长度为foo而不是foo.Length)。但另见下一颗子弹。通常,在F#中,使用面向对象编程作为软件工程设备是优选的。此策略还提供了一些工具优势,例如Visual Studio的“Intellisense”功能,通过“点入”对象来发现类型上的方法。

当想要编写类层次结构时,请三思而后行,是否可以用有区别的联合类型替换继承。您可以使用记录,类等来处理成员(静态成员或实例成员):

type Animal = 
    | Cat | Dog
    member this.Sound = match this with | Cat -> "meow" | Dog -> "bark"
    static member FromString s = function | "cat" -> Cat | "dog" -> Dog | _ -> failwith "nope."
相关问题