关于数据抽象的探讨

源自 此演讲 中的此页:

Babara Liskov 论文:Programming with Abstract Data Types

圆与椭圆问题 wiki 页 开头简介:

The circle–ellipse problem in software development (sometimes called the square–rectangle problem) illustrates several pitfalls which can arise when using subtype polymorphism in object modelling. The issues are most commonly encountered when using object-oriented programming (OOP). By definition, this problem is a violation of the Liskov substitution principle, one of the SOLID principles.

The problem concerns which subtyping or inheritance relationship should exist between classes which represent circles and ellipses (or, similarly, squares and rectangles).

在我看来,这是一个应用领域的问题。讨论这个问题应该在具体应用场景中讨论。在编程语言中,至少包含语法和标准库2个方面,语法层面应该不涉及这种分类问题,在标准库层面可能会遇到。

我在学校学到的“圆是特殊的椭圆”、“正方形是特殊的长方形”这种说法。但 “特殊”在编程领域对应的词语是什么? 是 实例化、满足特定要求的值、还是 子类?

分类问题是系统工程的一部分,一个事物从不同角度和层面去观察,得到的分类结果可能也是不同的。比如 人,可以分为男人和女人,也可以分为成年人和未成年人,关键在于我们观察的角度和层面。

我猜从具体应用的场景出发,应该很容易得到结论。
但是作为标准库,可能更关注“能不能完成某些基本功能”,而不是“分类问题”。所以我的想法是,只提供椭圆接口,因为能画椭圆,一定能画正圆。

比如,订单,如何定义个订单,订单应该包含哪些字段,这不是标准库能决定的。
同理,标准库的椭圆,并不一定和业务层面的椭圆是等价的,业务层面可以根据自己的业务来表达正圆和椭圆,这个是根据实际需求来决定的。标准库不应该,也没办法处理更上层业务的问题。

这段讨论没有正面回答问题,而是提供一个思路,把问题抛给使用场景。
正圆到底是不是椭圆,谁要用,谁最清楚。

1 个赞

@ice1000 关于椭圆可被视为圆的扩展,wiki里“Challenge the premise of the problem”一个视角挺有意思:

By analogy, then, a Circle is not an Ellipse, because it lacks the same degrees of freedom as an Ellipse.

Applying better naming, then, a Circle could instead be named OneDiameterFigure and an ellipse could be named TwoDiameterFigure. With such names it is now more obvious that TwoDiameterFigure should extend OneDiameterFigure, since it adds another property to it; whereas OneDiameterFigure has a single diameter property, TwoDiameterFigure has two such properties (i.e., a major and a minor axis length).

这段我有看到,并且赞同正圆不是椭圆的说法。
但是 “圆是特殊的椭圆” 本身是普通人对两者关系非常朴素的认知和描述,所以“因为圆的要求更高,所以不是椭圆”本身是与不同人朴素的认知相悖的。这个问题我认为应该理解为,椭圆可以画成正圆,并且表现出正圆的性质,但他本身不是正圆。就像 人形机器人可以看起来像人类并行为举止像人类,但他不是人类。
而使用“单直径图”“双直径图”这种更精确的定义,比大多数人更专业,学习成本更高。我建议还是保留“正圆”和“椭圆”的叫法。
但是这带来的下一个问题是,语言层面的类型兼容性。一个表现出正圆性质的椭圆,能否当做正圆使用?这在数学上显然是可以的,但是如何在编程的语言系统上表现出相同的兼容性?
语言层面是否要保证这个兼容性?我认为应该是不需要的。

如果特定领域的库需要,他应该自己来协调这个关系,由这个库来定义正圆和椭圆的关系。
不在语法层面去讨论正圆和椭圆的关系,就可以减少很多不必要的问题,比如 类型转换 和 逻辑的兼容。而正圆和椭圆的转换,根本在于椭圆的2个轴长是否相等,这显然已经到了逻辑层面,而不是语法层面。
语法层面可以有鸭子类型,“你所包含的属性符合某种类型的定义”,但 圆和椭圆 是 “你的行为符合某种类型的定义”,这就要在运行时去解决了。
或许就是在某个三方库里重新定义圆,他同时满足正圆和椭圆的要求,并通过某个方法来表达 是正圆还是椭圆。并且在内部保证了逻辑的统一,还要保证外部的可感知,比如提供一个“IsPerfectly()”。
在逻辑层提供一个自协调的逻辑分类系统,与语言的分类并不冲突,而是额外增加了针对特定领域的扩展。
我认为语言应该提供一个更具扩展性的、简单的、容易理解的、静态类型系统,而非严格定义的、满足一切需求的类型系统。

1 个赞

我认为语言应该提供一个更具扩展性的、简单的、容易理解的、静态类型系统,而非严格定义的、满足一切需求的类型系统。

借用 wiki的例子(“Description”部分),当下大多数OO语言的类型系统设计是子类(subclass)必须实现基类(base class)的所有接口。在此设计下:

  1. 假如将圆定义为椭圆的子类,而椭圆类添加了“拉伸x轴”接口,那么圆类必须实现此接口,但会导致圆不再为圆。
  2. 假如反过来将椭圆定义为圆的子类,而圆类添加了“求半径”接口,那么椭圆必须实现此接口,但无法实现。

从设计的角度看,当前的这一基类-子类关系设计是否有改良的空间呢?抑或在大多数场景下不会有类似问题因而不值得将此设计其复杂化、由开发者自行规避?

看到狮偶中支持 添加数据类型方法,当前的设计是可以自定义类型但各类型间除了引用之外没有其他关联吗?这样的话的确规避了继承导致的问题。

顺便请问一下,每个名称后面的“类型”是何意呢?比如“名称 id 类型”:

UI没有调好。名称是ID 类型是基本类型整数。 :rofl:

1 个赞

目前类型之间只能引用。数据结构 跟 C语言的结构体类似,没有函数,没有逻辑,纯数据。需要复用就组合。

用 degrees of freedom 来解释是合理的,但我觉得大部分持有「椭圆是圆的扩展」这一看法的人是不理解什么叫 degrees of freedom 的

看到 matplot.patches.Circles 是把圆作子类的,还正好有 ellipse.set_height 这个导致圆不为圆的接口。

JavaFx 是把两者并列为 Shape 子类

反过来的尚未找到。

个人猜度,也许在API设计时,如果 椭圆 的专用接口太多,塞进 圆 里感觉不合适,就倾向把椭圆作为子类了。

在我的理念里,操作是最基础的,应该从"操作"来推导出继承链.
世界是由操作组成的,
比如zfc系统,{},{{}}内涵其实就是集合操作
比如另一种操作:给字符后面添加字符’+’
那么我们就有了+,++,+++,++++
比如lambda表达式的替换操作
只要有一种操作和一个对象,就可以创造世界.

所以园和椭圆两种对象,谁继承谁,看各自有什么操作.

现实的自由度很难被可数编码.

现在的编程语言走类型的路子搞的太复杂了。
本来大部分编程语言的核心是图灵机模型,但是嵌入了类型系统后,相当于一个语言里有两套完备的数学模型,复杂度直接上升到天花板。

改善图灵机的过程模型,而不是为了抽象直接在一个系统里嵌入另一个系统,或许是一种可以尝试的改进。