首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用F#中的类(可变vs不可变/成员与自由函数)

使用F#中的类(可变vs不可变/成员与自由函数)
EN

Stack Overflow用户
提问于 2016-08-12 17:03:40
回答 3查看 1.1K关注 0票数 16

我目前正在做exercism.io F#轨道。对于不知道的人来说,学习或改进编程语言是解决小问题的TDD风格。

最后两个任务是关于在F#中使用类(或在F#中称为类的类型)。其中一个任务使用具有平衡和状态(打开/关闭)并可以通过使用函数来更改的BankAccount。用法如下(取自测试代码):

代码语言:javascript
复制
let test () =
    let account = mkBankAccount () |> openAccount
    Assert.That(getBalance account, Is.EqualTo(Some 0.0)

我使用一个不可变的BankAccount类编写了使测试通过的代码,该类可以使用空闲函数进行交互:

代码语言:javascript
复制
type AccountStatus = Open | Closed

type BankAccount (balance, status) =
    member acc.balance = balance
    member acc.status = status

let mkBankAccount () =
    BankAccount (0.0, Closed)

let getBalance (acc: BankAccount) =
    match acc.status with
    | Open -> Some(acc.balance)
    | Closed -> None

let updateBalance balance (acc: BankAccount) =
    match acc.status with
    | Open -> BankAccount (acc.balance + balance, Open)
    | Closed -> failwith "Account is closed!"

let openAccount (acc: BankAccount) =
    BankAccount (acc.balance, Open)

let closeAccount (acc: BankAccount) =
    BankAccount (acc.balance, Closed)

在开始学习F#之前做了大量的面向对象操作,这一次让我感到奇怪。更有经验的F#开发人员如何使用类?为了更简单地回答这个问题,下面是我对F#中的类/类型的主要关注:

  • 在F#中,是否不鼓励以典型的面向对象方式使用类?
  • 不可变类优先吗?(在上面的例子中,我发现它们令人困惑)
  • 在F#中访问/更改类数据的首选方法是什么?(类成员函数和获得/设置或释放允许管道的函数?)静态成员是否允许管道和为函数提供合适的命名空间?)

如果问题含糊不清,我很抱歉。我不想在我的函数代码中养成坏的编码习惯,我需要一个关于什么是好的实践的起点。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-08-12 20:59:12

在F#中,是否不鼓励以典型的面向对象方式使用类?

这并不气馁,但这并不是最有经验的F#开发人员第一次去的地方。大多数F#开发人员将避免子类化和面向对象的范式,而是使用记录或受歧视的联合以及对它们进行操作的函数。

不可变类优先吗?

在可能的情况下,应优先考虑不变性。也就是说,不可变类通常可以用其他方式表示(见下文)。

在F#中访问/更改类数据的首选方法是什么?(类成员函数和获得/设置或释放允许管道的函数?)静态成员是否允许管道和为函数提供合适的命名空间?)

这通常是通过允许管道的函数来完成的,尽管访问也可以直接完成。

对于上面的代码,更常见的做法是使用记录而不是类,然后将处理记录的函数放入模块中。像您这样的“不可变类”可以更简洁地作为记录编写:

代码语言:javascript
复制
type BankAccount = { balance : float ; status : AccountStatus }

完成此操作后,使用它变得更容易,因为您可以使用with返回修改后的版本:

代码语言:javascript
复制
let openAccount (acc: BankAccount) =
    { acc with status = Open }

请注意,将这些函数放入模块中是很常见的:

代码语言:javascript
复制
module Account =
    let open acc =
       { acc with status = Open }
    let close acc =
       { acc with status = Closed }
票数 12
EN

Stack Overflow用户

发布于 2016-08-12 20:58:15

问题:在F#?中,是否不鼓励以典型的面向对象方式使用类?

这并不违背F#的本性,我认为这是有道理的。

但是,如果开发人员希望充分利用F#的优势(例如类型干扰、使用部分应用程序等功能模式的能力、简洁性),并且不受遗留系统和库的限制,那么类的使用就应该受到限制。

F#的乐趣和利润给出了一个使用类的利弊的快速总结。

调度:不可变类优先吗?(在上面的例子中,我发现它们令人困惑)

有时是,有时不是。我认为类的不可变性给您带来了许多优势(更容易解释类型的不变量等)。但是有时不可变类的使用可能有点麻烦。

我认为这个问题有点过于宽泛--它有点类似于fluent接口在面向对象设计中是否更受欢迎的问题--简单的回答是:它取决于。

在F#中访问/更改类数据的首选方法是什么?(类成员函数和获得/设置或释放允许管道的函数?)静态成员允许管道和为函数提供一个合适的命名空间如何?)

管道是F#中的一个规范结构,所以我选择静态成员。如果您的库是用其他语言使用的,那么您也应该在类中包括getter和setter。

编辑:

FSharp.org列出了一系列非常具体的设计准则,其中包括:

根据标准的面向对象方法,✔确实使用类封装可变状态。 ✔确实使用受歧视的联合作为类层次结构的替代来创建树结构数据。

票数 7
EN

Stack Overflow用户

发布于 2016-08-13 00:52:47

有几种看待这个问题的方法。

这可能意味着几件事。对于POCO,首选是不变的F# records。然后,对它们的操作返回新的记录,所需的字段发生了更改。

代码语言:javascript
复制
type BankAccount { status: AccountStatus; balance: int }
let close acct = { acct with status = Closed } // returns a *new* acct record

因此,这意味着你必须克服帐户“对象”的概念,它代表一个单一的“事物”。它只是您操作的数据,以创建不同的数据,并最终(很可能)存储在某个数据库中。

所以,与其使用OO范式acct.Close(); acct.PersistChanges(),不如使用let acct' = close acct; db.UpdateRecord(acct')

然而,对于“面向服务的体系结构( F# )”中的“服务”,接口和类在中是非常自然的。例如,如果您想要一个Twitter,您可能会创建一个类来包装所有的HTTP调用,就像在C#中一样。我在F#中看到了一些关于完全避开SOA的“坚实”意识形态的参考,但我从未想过如何在实践中实现这一点。

就我个人而言,我喜欢上面有Suave的FP组合子的FP -OO三明治,中间使用Autofac的SOA,底部的FP记录。我发现它工作得很好,而且是可伸缩的。

如果BankAccount没有平衡的话,FWIW你可能也想让你的Closed成为一个受歧视的联盟。在您的代码示例中尝试此方法。F#的优点之一是它使不合逻辑的状态无法表示。

代码语言:javascript
复制
type BankAccount = Open of balance: int | Closed
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38923143

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档