使用函数式编程样式数据类型进行模型继承

时间:2010-09-04 16:15:59

标签: inheritance f# functional-programming types

我最近一直在使用F#,并尝试以功能方式编写代码,而不是以不同的语法再次执行OOP。我现在遇到了一个问题,我可以通过混合继承和有区别的联合来解决这个问题,但我正试图找到一个纯粹的功能样式表示。

我想要建模的是这样的(改为保留模式,因为我不能使用实际的代码):

type Shape =
    | Rectangle of Size * Size
    | Circle of Diameter

到目前为止一切顺利,但现在我需要代表一系列与不同类型的形状相关的其他属性,例如:

type ShapeProperty =
    | Color of Shape * Color // Fine, valid for all shapes
    | Rotation of Shape * Angle // Wants to be Rotation of Rectangle * Angle
    | Details of Shape * int // Wants to be Detail of Circle * int

如果不使用Shape的区别联合,我使用了基类和继承,我可以参考实际类型并确保Rotation只能应用于Rectangle而不是Circle,但现在我不能。有没有一种方法可以实现类似的东西,同时仍然保持纯粹的功能数据结构?

修改

我目前的解决方案是将单个形状的定义与形状完全相关的事实分开,如下所示:

type Rectangle = Rectangle of Size * Size // Or using a record type
type Circle = Circle of Diameter // Or using a record type
type Shape = RectangleShape of Rectangle | CircleShape of Circle

这意味着我有在ShapeProperty中引用的类型:

type ShapeProperty =
    | Color of Shape * Color
    | Rotation of Rectangle * Angle
    | Details of Circle * int

这感觉有点笨拙,因为现在需要封装Shape类型中的每个形状以将它们存储在集合中,但它确实给了我一种表达我所追求的类型安全性的方法。对此的任何改进都是受欢迎的。

1 个答案:

答案 0 :(得分:9)

我认为受歧视的工会可能只是这部分工作的错误工具。仅仅因为你正在编写功能代码并不意味着你应该抛弃你所知道的一切。

歧视的工会(在某些方面,并且有一点需要注意)继承的反转(派生类型),我们可以将它们称为组合类型。

在派生类型模型中,关于父级的所有内容都保证对于子级(LSP)是正确的,在这个组合的类型模型中,关于子级的所有内容都保证对父级是真实的。(即代替“新的狗:动物” ,新的猫:动物,它是新的狗,猫:猫狗,所以你得到了关于猫和狗的所有陈述的新结构。把它想象成猫和狗的所有陈述的交集。)

那么问题就变成了,“我们如何使用OCaml中的派生类型?”那么,那里的“O”确实代表了对象......

我建议你为你的形状heiarchy创建一个派生类型的对象模型,因为你有一个顶部类型(形状)和一个底部类型(空形状,或null)。然后你用这些对象组成你的ADT,所以你有

抽象形状, 圆形:形状, 矩形:形状,

然后你有

输入ShapeProperty =     | Foo of Shape     |矩形条     | Blah of Circle ;;

任何了解形状属性的东西都必须能够处理所有这三种情况(合理的,因为这就是你所拥有的),圆形和矩形的分配与形状兼容,所以你不必担心关于拳击惯例。

对于它的价值,YMMV等Haskell(运行的另一个主要ML方言)不使用派生类型的对象,它使用所谓的“类型类”,它们是你“属性/行为的集合”派生自“和”保证“允许函数接受类型类的具体实例”的实现。

虽然在它的哲学中功能更强大,但试图弄清楚发生了什么让我头疼,因为我用于像对象之类的结构的传统抽象不再适用,我必须使用逻辑等效的这些结构,但语义上实际上更不寻常(虽然我知道很多人使用它们取得了巨大的成功,所以它可能只是我自己的失败)。

请注意,由于标记了联合标记,因此您需要做的就是保证所有标记的语义操作。那个动作可能是“失败的'为什么你给我其中一个,我不知道该怎么办'”。你注意到这个结构的问题是你在这样做时失去了许多非常酷的类型安全方面。例如,你编写一个接受int选项的函数,然后如果该选项不是你抛出异常,那么,为什么你说你知道如何处理一个选项int?