【问题标题】:Model inheritance using functional programming style data types使用函数式编程风格数据类型的模型继承
【发布时间】:2011-04-08 06:25:42
【问题描述】:

我最近一直在使用 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 使用区分联合,而是使用基类和继承,我可以引用实际类型并确保旋转只能应用于 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 类型中以将它们存储在一个集合中,但它确实为我提供了一种表达我所追求的类型安全的方法。欢迎对此进行任何改进。

【问题讨论】:

  • 您打算如何使用 ShapeProperty 类型?我认为您需要在这里进行不同的设计,但是如果没有更多细节,很难说哪种设计是合适的。
  • 我需要 ShapeProperty(和其他类似的类)来添加各种形状的属性,而不必触及形状本身的定义,因为在我的真实案例中,这些属性仅在使用形状时才相关在某些情况下,但不是所有其他情况。基本上我需要一种类型安全的表示,它可以让我进行转换和查询,例如“查找所有旋转 10 的形状”。
  • 这是什么意思?圆的旋转是否小于 10? (圆圈没有你描述的旋转的概念。)我认为你需要更多关于你想要什么的细节。
  • 这个例子绝对不是完美的,我的意思是找到所有旋转 10 的形状是对一组 ShapeProperty 项目执行搜索,选择每个属性(以及每个形状)预期的类型和预期的值。在我的示例中,圆没有旋转,因为旋转圆(围绕其中心)不会改变它,在实际代码中,如果类型不支持“形状”,则这些属性显然根本不适用。
  • 首先,围绕中心旋转一个圆圈可能会改变它,但我猜这是示例的缺陷,所以我将忽略它。其次,作为一个试图做你想做的事情的人,我可以告诉你这是一个坏主意。对于所有标签(这是 LSP 的变体),可区分联合的属性必须为真,否则它们不属于同一个联合。我会警告你,当我按照你想出的方式做这件事时,我正在与类型系统作斗争(矩形是矩形,让我将它作为参数传递而无需装箱)。

标签: inheritance f# functional-programming types


【解决方案1】:

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

可区分联合(在某些方面,并且有一点需要注意)继承的逆(派生类型),我们可以称它们为组合类型。

在派生类型模型中,关于父级的所有内容都保证对于子级(LSP)是真实的,在这个组合类型模型中,关于子级的所有内容都保证对于父级是真实的。(即,而不是“新狗:动物, new cat : animal, it's new Dog, Cat : CatDog, 所以你得到了所有关于猫和狗的陈述的新结构。把它想象成关于猫和狗的所有陈述的交集。)

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

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

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

那么你有

类型 ShapeProperty = |形状的Foo |矩形条 |圆的废话;;

任何知道 shape 属性的东西都必须能够处理所有这三种情况(合理,因为这就是你拥有它的原因),并且圆形和矩形的赋值与 shape 兼容,所以你不必担心关于拳击惯例。

对于它的价值,YMMV 等 Haskell(另一种主要的 ML 方言)不使用派生类型的对象,它使用所谓的“类型类”,它们是您“派生自”和“保证实现”,从而允许函数接受类型类的具体实例。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多