F#:将顶级函数转换为成员方法的优势?

J C*_*per 5 methods f# function

之前我曾就我的第一个F#项目请求了一些反馈.在结束问题之前,因为范围太大,有人很友好地查看并留下一些反馈.

他们提到的一件事是指出我有许多常规函数可以转换为我的数据类型的方法.我尽职尽责地改变了这样的事情

let getDecisions hand =
    let (/=/) card1 card2 = matchValue card1 = matchValue card2
    let canSplit() = 
        let isPair() =
            match hand.Cards with
            | card1 :: card2 :: [] when card1 /=/ card2 -> true
            | _ -> false
        not (hasState Splitting hand) && isPair()
    let decisions = [Hit; Stand]
    let split = if canSplit() then [Split] else []
    let doubleDown = if hasState Initial hand then [DoubleDown] else []
    decisions @ split @ doubleDown
Run Code Online (Sandbox Code Playgroud)

对此:

type Hand
// ...stuff...
member hand.GetDecisions =
    let (/=/) (c1 : Card) (c2 : Card) = c1.MatchValue = c2.MatchValue
    let canSplit() = 
        let isPair() =
            match hand.Cards with
            | card1 :: card2 :: [] when card1 /=/ card2 -> true
            | _ -> false
        not (hand.HasState Splitting) && isPair()
    let decisions = [Hit; Stand]
    let split = if canSplit() then [Split] else []
    let doubleDown = if hand.HasState Initial then [DoubleDown] else []
    decisions @ split @ doubleDown
Run Code Online (Sandbox Code Playgroud)

现在,我不怀疑我是一个白痴,但除了(我猜)让C#interop变得更容易,这是什么让我受益?具体来说,我发现一对夫妇DIS优势,不计算转换(我不会算,因为我可以通过这种方式在第一时间已经做了额外的工作,我认为,尽管这已经使用F#互动更多的做痛苦).首先,我现在不再能够轻松地使用"流水线"功能了.我不得不去改some |> chained |> calls(some |> chained).Calls等等.此外,它似乎让我喜欢的类型系统笨-而我原来的版本,我的程序不需要任何类型的注释,在很大程度上转化为成员方法后,我得到了一堆错误约正在查找在那时不确定,(/=/) 以上).

我希望自己没有太可疑,因为我很欣赏我收到的建议,而写作惯用代码对我来说非常重要.我只是好奇为什么这个成语是这样的:)

谢谢!

Yin*_*Zhu 7

我在F#编程中使用的一种做法是在两个地方使用代码.

实际的实现很自然地放在一个模块中:

module Hand = 
  let getDecisions hand =
    let (/=/) card1 card2 = matchValue card1 = matchValue card2
  ...
Run Code Online (Sandbox Code Playgroud)

并在成员函数/属性中给出一个"链接":

type Hand
  member this.GetDecisions = Hand.getDecisions this
Run Code Online (Sandbox Code Playgroud)

这种做法通常也用于其他F#库,例如powerpack中的矩阵和向量实现matrix.fs.

在实践中,并非每个功能都应放在两个地方.最终决定应基于领域知识.

关于顶层,实践是将函数放入模块中,并在必要时将其中一些放在顶层.例如,在F#PowerPack中,Matrix<'T>位于命名空间中Microsoft.FSharp.Math.在顶层中为矩阵构造函数创建快捷方式很方便,这样用户可以直接构造矩阵而无需打开命名空间:

[<AutoOpen>]
module MatrixTopLevelOperators = 
    let matrix ll = Microsoft.FSharp.Math.Matrix.ofSeq ll
Run Code Online (Sandbox Code Playgroud)


Bri*_*ian 4

成员的一个优势是智能感知和其他使成员易于发现的工具。当用户想要探索一个对象时foo,他们可以键入foo.并获取该类型的方法列表。成员的“扩展”也更容易,因为你最终不会在顶层出现数十个名字;随着程序大小的增长,您需要更多名称,只有在符合条件时才可用(someObj.Method 或 SomeNamespace.Type 或 SomeModule.func,而不仅仅是 Method/Type/func“浮动自由”)。

正如您所看到的,它也有缺点;类型推断尤其值得注意(需要先知道x调用的类型x.Something);对于非常常用的类型和功能,提供成员和函数模块可能会很有用,以获得两者的好处(例如,FSharp.Core 中的常见数据类型会发生这种情况)。

这些是“脚本便利性”与“软件工程规模”之间的典型权衡。我个人总是倾向于后者。