重用 F# 类型的共享字段的最佳方式是什么?

What is the best way to reuse shared fields with F# types?

我正在编写一个 F# 程序来管理项目列表。所有项目都有名称和价格。有有保质期的食品和有保修期的非食品。我第一次尝试对这个域建模如下:

type Info = {Name : string; Price : decimal;} 

type Item = 
    | Food of Info * DateTime
    | NonFood of Info * TimeSpan

let increasePrice item diff =
    let modifyPrice info =
        {info with Price = info.Price + diff}
    match item with
    | Food (info,expirationDate) -> Food ( modifyPrice info,expirationDate)
    | NonFood (info,warranty) -> NonFood ( modifyPrice info,warranty)

但我不确定这样做是否正确。正如您所看到的,这是用一个(抽象)基础 class 和两个派生子 class 的 OO 概念建模的。我特别不满意函数 increasePrice 必须区分两种类型的 Item,即使它只修改 Item.[=14 的一般属性=]

是否有更好的方法来使用其他方法设计这样的项目列表?

这种方法总体上是可以的。与 OO 相比,函数式编程通常需要更少的仪式和教条思维。像“function has to distinct ... even though ...”这样的问题通常不被认为是值得关注的,除非有一些实际的实际原因。


不过,如果(出于某些实际原因)您希望食物和非食物物品看起来相似,除非它们的差异实际上很重要,实现这一点的一种方法是嵌套您的数据结构反过来:

type Item = { Name: string; Price: decimal; kind: ItemKind }
type ItemKind = Food of DateTime | NonFood of TimeSpan

let increasePrice item diff = { item with Price = item.Price + diff }

尚不清楚哪种方法“更好”。它们各有利弊,选择取决于您将如何使用这些类型(和函数)。

另一种方法就是故意复制字段,就像这样。这种方法并不少见。

type Food = { Name: string; Price: decimal; ExpirationDate: DateTime }
type NonFood = { Name: string; Price: decimal; Warranty: TimeSpan }
type Item =
    | Food of Food
    | NonFood of NonFood

一般来说,像这样复制字段不一定有什么问题,即使每种项目都有一些共同的字段。如果由于许多 DU 案例 and/or 许多相等字段而导致代码重复或类型大量重复,那么是时候考虑另一种方法了。否则,这种方法实际上可以有助于简化代码 - 更容易推理,更容易阅读。

字段 Name 和 Price 是公共结构中的相同字段,还是不同 DU 中具有相同名称的不同字段,实际上通常并不重要。仅当某种形式的代码重复出现得太频繁时,您才需要考虑另一种方法是否更好。

因此,在您的特定情况下,这不一定是一个好的解决方案,我假设项目种类 and/or 字段的数量可能不会那么有限。

您只需根据具体情况进行判断。