我应该编写利用 Intellisense 的代码吗?
Should I write code that takes advantage of Intellisense?
我从 F# 开始,在理解语法方面取得了一些进步。但是,我仍然不清楚使用 F# 功能的最佳方式。在我来自的Python,通常有一种"best"(几乎是规范的)做事方式。也许 F# 也是如此,但我还没有弄清楚。所以我下面的问题是关于使用 F# 的最佳方式,而不是关于 F# 语法的技术问题。
最近我看到了 Eric Meijer 博士的视频 (C9 Lectures - Functional Programming Fundamentals Chapter 2 of 13)
,其中 Meijer 博士称赞了 OOP 的点符号,观察到它允许 Intellisense 显示可用方法列表。他感叹这种设施在纯 FP 中是不可用的,它通过帮助程序员使编程变得如此容易 "move forward."
一些实验表明,Intellisense 当然可以与 F# 类 一起使用,但也可以与 F# 记录一起使用,后者与 类 一样使用点表示法。这意味着可以调整代码以便利用 Intellisense,而无需一直编写 类(我假设在 F# 中 类 比记录更重更慢,如果我认为请纠正我我错了)。
以下代码展示了执行相同操作的两种编写代码的方法(称它们为 "versions"):
// 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 更 "significant" (我将保留该术语未定义)基于代码组织和使用 Intellisense 的能力,编码策略是合理的。另一方面,由于 OOP 的复杂性,我宁愿留在 "pure" FP 领域。
使用记录是否是两种极端替代方案(OOP 和纯 FP)之间值得妥协的方法?
一般来说,给定三个备选方案(纯 FP、上述记录或 类)在什么情况下优先选择一个备选方案的一般准则是什么?
最后,是否有其他可用的编码策略可以帮助我组织代码 and/or 利用 Intellisense?
Intellisense 在 F# 中仍然可以正常工作,但在模块级别而不是 class 级别。也就是说,我只是输入了 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
跟进。然而,您在 "proper" F# 风格中保留了函数 "clean" 和柯里化。
最后,关于功能设计的另一条建议。您提到有一个函数,其中一个参数(如 add3
和 sub3
函数中的 a
)在某种程度上比其他参数多 "significant"。在 F# 中,任何此类参数都应该是 last 参数,因为这允许您使用 |>
运算符将其放入函数链中,如下所示:
let a = 20
a |> AddSub.add3 5 10 |> AddSub.sub3 2 3 // Result: 30
或者更确切地说,当从单个起始值有 "pipeline" 操作时,使用大多数人喜欢的样式:
let a = 20
a
|> AddSub.add3 5 10
|> AddSub.sub3 2 3
// Result: 30
当其中有更多操作时,垂直排列管道变得更加重要。我的经验法则是,如果管道中指定的总 "extra" 参数超过两个(上面的管道有四个 "extra" 参数,add3
和 [=23= 各有两个] 函数),或者如果 "extra" 参数中的任何一个比单个值更复杂(例如,如果一个参数是匿名函数,如 (fun x -> sprintf "The value of x was %d" x)
或类似函数),那么您应该将其垂直排列。
P.S。如果您还没有阅读它,请阅读 Scott Wlaschin 在 Thinking Functionally 上的精彩系列。它将有助于解释有关此答案的许多事情,例如为什么我建议将 "most significant" 参数放在最后。如果您没有立即理解我关于如何使您能够将它与 |>
参数一起使用的简短评论,或者如果您对这个答案还有其他疑惑,那么您可能会受益匪浅出自 Scott 的文章。
你的问题很宽泛,@munn 已经涵盖了模块的一些具体方面。我想补充一些想法,这些想法是我自己在一个不断变化的开发团队中处理一个相当大的 F# 代码库时产生的。
代码可发现性规则。随着代码库的增长,能够(通过 Intellisense)查看对象可用的方法变得越来越重要。但也许更重要的是,它可以帮助您团队的新成员,他们可能还不知道模块 X
具有处理 class/record/etc.
实例的所有方法
我找到了 F# component design guide very helpful. It provides a lot of details on how to strike the balance between OOP and functional. For your specific question, see the section on intrinsic operations,其中直接引用了您提出的观点:
Do use properties and methods for operations intrinsic to types.
This is called out specifically because some people from a functional programming background avoid the use of object oriented programming together, preferring a module containing a set of functions defining the intrinsic functions related to a type (e.g. length foo rather than foo.Length). But see also the next bullet. In general, in F#, the use of object-oriented programming is preferred as a software engineering device. This strategy also provides some tooling benefits such as Visual Studio’s “Intellisense” feature to discover the methods on a type by “dotting into” an object.
当想要编写 class 层次结构时,请三思是否不能用可区分的联合类型替换继承。您可以像使用记录、classes 等一样添加成员(静态或实例成员):
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."
我从 F# 开始,在理解语法方面取得了一些进步。但是,我仍然不清楚使用 F# 功能的最佳方式。在我来自的Python,通常有一种"best"(几乎是规范的)做事方式。也许 F# 也是如此,但我还没有弄清楚。所以我下面的问题是关于使用 F# 的最佳方式,而不是关于 F# 语法的技术问题。
最近我看到了 Eric Meijer 博士的视频 (C9 Lectures - Functional Programming Fundamentals Chapter 2 of 13)
,其中 Meijer 博士称赞了 OOP 的点符号,观察到它允许 Intellisense 显示可用方法列表。他感叹这种设施在纯 FP 中是不可用的,它通过帮助程序员使编程变得如此容易 "move forward."
一些实验表明,Intellisense 当然可以与 F# 类 一起使用,但也可以与 F# 记录一起使用,后者与 类 一样使用点表示法。这意味着可以调整代码以便利用 Intellisense,而无需一直编写 类(我假设在 F# 中 类 比记录更重更慢,如果我认为请纠正我我错了)。
以下代码展示了执行相同操作的两种编写代码的方法(称它们为 "versions"):
// 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 更 "significant" (我将保留该术语未定义)基于代码组织和使用 Intellisense 的能力,编码策略是合理的。另一方面,由于 OOP 的复杂性,我宁愿留在 "pure" FP 领域。
使用记录是否是两种极端替代方案(OOP 和纯 FP)之间值得妥协的方法?
一般来说,给定三个备选方案(纯 FP、上述记录或 类)在什么情况下优先选择一个备选方案的一般准则是什么?
最后,是否有其他可用的编码策略可以帮助我组织代码 and/or 利用 Intellisense?
Intellisense 在 F# 中仍然可以正常工作,但在模块级别而不是 class 级别。也就是说,我只是输入了 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
跟进。然而,您在 "proper" F# 风格中保留了函数 "clean" 和柯里化。
最后,关于功能设计的另一条建议。您提到有一个函数,其中一个参数(如 add3
和 sub3
函数中的 a
)在某种程度上比其他参数多 "significant"。在 F# 中,任何此类参数都应该是 last 参数,因为这允许您使用 |>
运算符将其放入函数链中,如下所示:
let a = 20
a |> AddSub.add3 5 10 |> AddSub.sub3 2 3 // Result: 30
或者更确切地说,当从单个起始值有 "pipeline" 操作时,使用大多数人喜欢的样式:
let a = 20
a
|> AddSub.add3 5 10
|> AddSub.sub3 2 3
// Result: 30
当其中有更多操作时,垂直排列管道变得更加重要。我的经验法则是,如果管道中指定的总 "extra" 参数超过两个(上面的管道有四个 "extra" 参数,add3
和 [=23= 各有两个] 函数),或者如果 "extra" 参数中的任何一个比单个值更复杂(例如,如果一个参数是匿名函数,如 (fun x -> sprintf "The value of x was %d" x)
或类似函数),那么您应该将其垂直排列。
P.S。如果您还没有阅读它,请阅读 Scott Wlaschin 在 Thinking Functionally 上的精彩系列。它将有助于解释有关此答案的许多事情,例如为什么我建议将 "most significant" 参数放在最后。如果您没有立即理解我关于如何使您能够将它与 |>
参数一起使用的简短评论,或者如果您对这个答案还有其他疑惑,那么您可能会受益匪浅出自 Scott 的文章。
你的问题很宽泛,@munn 已经涵盖了模块的一些具体方面。我想补充一些想法,这些想法是我自己在一个不断变化的开发团队中处理一个相当大的 F# 代码库时产生的。
代码可发现性规则。随着代码库的增长,能够(通过 Intellisense)查看对象可用的方法变得越来越重要。但也许更重要的是,它可以帮助您团队的新成员,他们可能还不知道模块 X
具有处理 class/record/etc.
我找到了 F# component design guide very helpful. It provides a lot of details on how to strike the balance between OOP and functional. For your specific question, see the section on intrinsic operations,其中直接引用了您提出的观点:
Do use properties and methods for operations intrinsic to types. This is called out specifically because some people from a functional programming background avoid the use of object oriented programming together, preferring a module containing a set of functions defining the intrinsic functions related to a type (e.g. length foo rather than foo.Length). But see also the next bullet. In general, in F#, the use of object-oriented programming is preferred as a software engineering device. This strategy also provides some tooling benefits such as Visual Studio’s “Intellisense” feature to discover the methods on a type by “dotting into” an object.
当想要编写 class 层次结构时,请三思是否不能用可区分的联合类型替换继承。您可以像使用记录、classes 等一样添加成员(静态或实例成员):
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."