模式匹配驱动的逻辑在现实世界的应用程序中是什么样子的?

Ant*_*off 5 oop pattern-matching

我偶然发现了以下段落(在阅读PEP-3119时,但问题是\xe2\x80\x99t 特定于语言的)。强调我的。

\n\n
\n

特别是,经常需要以对象类的创建者没有预料到的方式处理对象。为每个对象构建满足该对象每个可能用户需求的方法并不总是最佳解决方案。此外,有许多强大的调度理念与严格封装在对象内的行为的经典 OOP 要求形成鲜明对比,例如规则或模式匹配驱动的逻辑

\n
\n\n

我\xe2\x80\x99m熟悉OOP:围绕反映概念或现实世界实体的对象构建的代码,封装状态,并且可以通过方法进行操作。

\n\n

规则或模式匹配驱动的逻辑如何工作?它是什么样子的?

\n\n

真实世界的示例(也许在 Web 应用程序后端域中?)将非常感激。这里\xe2\x80\x99是OOP中的相应示例。

\n

Aad*_*hah 2

我相信PEP-3119文章描述了表达问题的解决方案。他们描述的解决方案是抽象基类

要理解抽象基类,首先阐明抽象实体和具体实体之间的区别将很有用。抽象实体没有实现。一个具体的实体有一个实现。面向对象编程中的实体通常是属性或方法。

面向对象编程语言中的是一组具体实体。一些面向对象的编程语言还具有作为抽象实体组的接口。抽象基类是实体的混合包。默认情况下,它的所有实体都是抽象的,但可以通过给它们一个默认实现来使它们具体化,如果需要,可以覆盖它。

Java中抽象基类的示例(如果我错了请纠正我):

abstract class Equals<T> {
    public boolean equals(T x) {
        return !notEquals(x);
    }

    public boolean notEquals(T x) {
        return !equals(x);
    }
}

class Person extends Equals<Person> {
    public firstname;
    public lastname;

    public Person(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname  = lastname;
    }

    public boolean equals(Person x) {
        return x.firstname == firstname &&
               x.lastname  == lastname;
    }
}
Run Code Online (Sandbox Code Playgroud)

无论如何,继续讨论表达问题。菲利普·瓦德勒对此有如下说法:

表达问题是一个老问题的新名称。目标是按情况定义数据类型,其中可以向数据类型添加新情况并在数据类型上添加新函数,而无需重新编译现有代码,同时保留静态类型安全性(例如,无强制转换)。

表达式问题涉及将所有数据类型切片和切块为可管理的片段,同时仍然允许任意扩展数据类型。数据类型可以可视化为案例和函数的二维矩阵。例如,考虑Document数据类型:

            Text       Drawing   Spreadsheet
        +-----------+-----------+-----------+
draw()  |           |           |           |
        +-----------+-----------+-----------+
load()  |           |           |           |
        +-----------+-----------+-----------+
save()  |           |           |           |
        +-----------+-----------+-----------+
Run Code Online (Sandbox Code Playgroud)

Document数据类型具有三种情况(TextDrawingSpreadsheet)和三个函数(drawloadsave)。因此,它被切成九个部分,可以用面向对象的语言(如 Java)实现,如下所示:

            Text       Drawing   Spreadsheet
        +-----------+-----------+-----------+
draw()  |           |           |           |
        +-----------+-----------+-----------+
load()  |           |           |           |
        +-----------+-----------+-----------+
save()  |           |           |           |
        +-----------+-----------+-----------+
Run Code Online (Sandbox Code Playgroud)

因此,我们将数据类型分割Document成九个可管理的部分。然而,我们选择首先将数据类型切片为函数,然后按情况将其切分。因此,添加新案例很容易(我们所做的就是创建一个实现该Document接口的新类)。但是,我们无法向界面添加新功能。因此,我们的数据类型不是完全可扩展的。

然而,面向对象的方法并不是分割数据类型的唯一方法。正如您强调的文字所说,还有另一种方法:

特别是,经常需要以对象类的创建者没有预料到的方式处理对象。在每个对象中构建满足该对象每个可能用户需求的方法并不总是最佳解决方案。此外,有许多强大的调度理念与严格封装在对象内的行为的经典 OOP 要求形成鲜明对比,例如规则或模式匹配驱动的逻辑

在面向对象的方式中,行为被严格封装在一个对象内(即每个类实现一组方法,在我们上面的示例中,是同一组方法)。另一种方法是规则或模式匹配驱动的逻辑,其中数据类型首先按情况切片,然后切成函数。例如,在 OCaml 中:

public interface Document {
    void draw();
    void load();
    void save();
}

public class TextDocument implements Document {
    public void draw() { /* draw text doc... */ }
    public void load() { /* load text doc... */ }
    public void save() { /* save text doc... */ }
}

public class DrawingDocument implements Document {
    public void draw() { /* draw drawing... */ }
    public void load() { /* load drawing... */ }
    public void save() { /* save drawing... */ }
}

public class SpreadsheetDocument implements Document {
    public void draw() { /* draw spreadsheet... */ }
    public void load() { /* load spreadsheet... */ }
    public void save() { /* save spreadsheet... */ }
}
Run Code Online (Sandbox Code Playgroud)

我们再次将数据类型分割Document成九个可管理的部分。然而,我们首先按情况对数据类型进行切片,然后将其切成函数。因此,添加新功能很容易,但添加新案例却不太可能。因此,数据类型仍然不完全可扩展。

这就是表达问题。如果我们先将数据类型切分为函数,那么添加新案例很容易,但添加新函数却很困难。如果我们首先按案例对数据类型进行切片,那么添加新函数很容易,但添加新案例却很困难。

表达式问题的出现是由于扩展数据类型的固有需要。如果数据类型永远不需要扩展,那么您可以使用这两种方法中的任何一种(我以后将其称为面向对象方法和函数式方法)。然而,对于大多数实际目的,数据类型确实需要扩展。

如果您只需要通过添加新案例来扩展数据类型,那么面向对象的方法是很好的(例如,在图形用户界面中,操作通常保持不变,但可以添加新的视觉元素)。如果您只需要通过添加新函数来扩展数据类型,那么函数式方法就很好(例如,我能想到的几乎所有通用程序)。

现在,如果需要通过添加新案例和新函数来扩展数据类型,那么这将是一个问题。但是,可以使用 JavaScript 和 Python 等动态语言使用检查( PEP-3119文章使用的词)来完成此操作。唯一的问题是,因为它是动态解决方案,所以编译器无法保证您已经实现了数据类型的所有部分,并且如果您回到表达式问题的定义,最后一个子句是and 同时保留静态类型安全性。因此,动态语言仍然没有解决表达问题。

无论如何,PEP-3119 文章讨论了调用检查作为选择数据类型的方法。首选调用,因为如果一个函数可以被调用,则也意味着它已被实现。检查是一种动态解决方案,因此并不总是正确的。

如果您想了解抽象基类如何解决表达式问题,那么我建议您阅读 PEP-3119 文章的其余部分。有关表达式问题的更多信息,我建议您阅读 Bob Nystrom 的博客文章“解决表达式问题”。