【问题标题】:F# code organization: types & modulesF# 代码组织:类型和模块
【发布时间】:2011-01-13 22:10:34
【问题描述】:

您如何决定是在模块内编写函数还是作为某种类型的静态成员?

例如,在 F# 的源代码中,有很多类型与同名模块一起定义,如下所示:

type MyType = // ...

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module MyType = // ...

为什么不简单地将操作定义为 MyType 类型的静态成员?

【问题讨论】:

    标签: f# module code-organization static-members


    【解决方案1】:

    这里有一些关于技术区别的说明。

    模块可以“打开”(除非它们具有 RequireQualifiedAccessAttribute)。也就是说,如果你把函数(FG)放在一个模块(M)中,那么你可以写

    open M
    ... F x ... G x ...
    

    而使用静态方法,您总是要编写

    ... M.F x ... M.G x ...
    

    模块函数不能重载。模块中的函数是 let-bound,而 let-bound 函数不允许重载。如果您希望能够同时调用两者

    X.F(someInt)
    X.F(someInt, someString)
    

    您必须使用一种类型的 members,该类型仅适用于“合格”调用(例如 type.StaticMember(...)object.InstanceMember(...))。

    (还有其他区别吗?我不记得了。)

    这些是影响选择另一种的主要技术差异。

    此外,F# 运行时 (FSharp.Core.dll) 中存在一些趋势,即仅将模块用于 F# 特定类型(在与其他 .Net 语言进行互操作时通常不使用)和用于 API 的静态方法语言更中立。例如,所有带有柯里化参数的函数都出现在模块中(柯里化函数在其他语言中调用起来很重要)。

    【讨论】:

    • “(还有其他区别吗?我不记得了。)”,让我试试:(1) F# 命名准则希望静态类成员以大写,一个模块 let-binding 与一个小写字母; (2) 类型可以扩展(使用with 语法),模块不能(不幸的是); (3) 类型的静态成员通常在第一次访问该类型时进行初始化,模块 let-binding 会更快地初始化,通常是在访问同一程序集中的相关类型时(这对于不是函数的成员或绑定非常重要) .
    • 还有一个相似之处:(4)静态成员在使用F#函数式语法时也不能重载(static member x a b不能重载,static member x(a, b)可以)。这样做的好处是静态成员也可以被柯里化,但缺点是它们变得更难从 C# 等中调用。
    【解决方案2】:

    在 F# 中,如果 ...

    1. 我必须定义类型而不考虑成员
    2. 该成员在功能上与我定义的类型相关

    【讨论】:

      【解决方案3】:

      除了其他答案之外,还有一种使用模块的情况:

      对于值类型,它们可以帮助定义不会在每次访问时重新评估的静态属性。例如:

      type [<Struct>] Point =
          val x:float
          val y:float
          new (x,y) = {x=x;y=y}
      
          static member specialPoint1 = // sqrt is computed every time the property is accessed
              Point (sqrt 0.5 , sqrt 0.5 )
      
      [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
      module Point = 
      
          let specialPoint2 = // sqrt is computed only once when the Module is opened
              Point (sqrt 0.5 , sqrt 0.5 )
      

      【讨论】:

        【解决方案4】:

        一些最初没有提到的重大区别:

        • 函数是 F# 中的一等值,但静态成员不是。所以你可以写objs |&gt; Seq.map Obj.func,但你不能写objs |&gt; Seq.map Obj.Member

        • 函数可以被柯里化,但成员不能。

        • 编译器会在调用函数时自动推断类型,但在调用成员时不会。所以你可以写let func obj = obj |&gt; Obj.otherFunc,但你不能写let func obj = obj.Member

        由于成员受到更多限制,我通常使用函数,除非我明确想要支持 OOP/C#。

        【讨论】:

        • "函数可以被柯里化,但成员不能。" - 不知道你的意思,在这里。对于 MyType 类型,我可以编写 static member goober a b c,然后使用 |&gt; MyType.goober a b 调用它。
        • @MiloDC - 你是对的。当我写这个答案时,我没有意识到这一点。但是,我通常不建议创建可curryable 成员,因为这会使 C# 互操作变得困难。
        猜你喜欢
        • 2018-01-08
        • 1970-01-01
        • 1970-01-01
        • 2012-01-15
        • 1970-01-01
        • 2011-03-30
        • 1970-01-01
        • 2011-12-25
        • 2011-04-20
        相关资源
        最近更新 更多