Haskell 中的面向对象编程

2024-01-07

我试图了解 Haskell 中的面向对象风格编程,因为我知道由于缺乏可变性,事情会有所不同。我尝试过类型类,但我对它们的理解仅限于它们作为接口。所以我编写了一个 C++ 示例,它是具有纯基类和虚拟继承的标准菱形。Bat继承Flying and Mammal,以及两者Flying and Mammal继承Animal.

#include <iostream>

class Animal
{
public:
    virtual std::string transport() const = 0;
    virtual std::string type() const = 0;
    std::string describe() const;
};

std::string Animal::describe() const 
    { return "I am a " + this->transport() + " " + this->type(); }

class Flying : virtual public Animal 
{
public:
    virtual std::string transport() const;
};

std::string Flying::transport() const { return "Flying"; }

class Mammal : virtual public Animal 
{
public:
    virtual std::string type() const;
};

std::string Mammal::type() const { return "Mammal"; }

class Bat : public Flying, public Mammal {};

int main() {
    Bat b;
    std::cout << b.describe() << std::endl;
    return 0;
}

基本上我对如何将这样的结构翻译成 Haskell 感兴趣,基本上这会让我有一个列表Animals,就像我可以有一个(智能)指针数组AnimalC++ 中的 s。


You just don't want to do that, don't even start. OO sure has its merits, but “classic examples” like your C++ one are almost always contrived structures designed to hammer the paradigm into undergraduate students' brains so they won't start complaining about how stupid the languages are they're supposed to use.

这个想法似乎基本上是通过编程语言中的对象来建模“现实世界的对象”。这可能是解决实际编程问题的好方法,但只有当您实际上可以在如何使用现实世界对象和如何在程序内处理 OO 对象之间进行类比时,它才有意义。

对于这样的动物例子来说,这真是荒谬。如果有的话,这些方法必须是“饲料”、“牛奶”、“屠宰”之类的东西……但“运输”是一个用词不当,我实际上会把它理解为“运输”。move动物,这更像是动物生活环境的一种方式,基本上只有作为访客模式的一部分才有意义。

describe, type and what you call transport are, on the other hand, much simpler. These are basically type-dependent constants or simple pure functions. Only OO paranoia&ddagger; ratifies making them class methods.

任何与动物有关的东西,基本上都有仅数据,如果你不尝试强制它变成类似于 OO 的东西,而是继续使用(有用的类型),就会变得更简单data在哈斯克尔。

因此,这个例子显然不会给我们带来任何进一步的帮助,让我们考虑一下 OOP 的一些内容does合理。我想到了小部件工具包。就像是

class Widget;

class Container : public Widget {
  std::vector<std::unique_ptr<Widget>> children;
 public:
  // getters ...
};
class Paned : public Container { public:
  Rectangle childBoundaries(int) const;
};
class ReEquipable : public Container { public:
  void pushNewChild(std::unique_ptr<Widget>&&);
  void popChild(int);
};
class HJuxtaposition: public Paned, public ReEquipable { ... };

为什么面向对象在这里有意义?首先,它允许我们轻松存储异构的小部件集合。这实际上在 Haskell 中并不容易实现,但在尝试之前,你可能会问自己是否真的需要它。毕竟,对于某些容器来说,可能不太希望允许这样做。在哈斯克尔中,参数多态性很好用。对于任何给定类型的小部件,我们观察以下功能Container几乎简化为一个简单的列表。那么为什么不直接使用列表呢,只要你需要Container?

当然,在这个例子中,你可能会发现你do需要异构容器;获得它们最直接的方法是{-# LANGUAGE ExistentialQuantification #-}:

data GenericWidget = GenericWidget { forall w . Widget w => getGenericWidget :: w }

在这种情况下Widget将是一个类型类(可能是抽象类的直译Widget)。在 Haskell 中,这是最后的手段,但可能就在这里。

Paned更多的是一个界面。我们可以在这里使用另一种类型类,基本上是音译 C++ 类型类:

class Paned c where
  childBoundaries :: c -> Int -> Maybe Rectangle

ReEquipable更困难,因为它的方法实际上改变了容器。那是明显地Haskell 中存在问题。但你可能会再次发现这是没有必要的:如果你已经替换了Container通过普通列表进行分类,您也许可以将更新作为纯功能更新进行。

不过,对于手头的任务来说,这可能效率太低了。充分讨论有效地进行可变更新的方法对于这个答案的范围来说太多了,但是这样的方法是存在的,例如使用lenses http://hackage.haskell.org/package/lens.

Summary

OO 并不能很好地转化为 Haskell。不存在一种简单的通用同构,只有多种近似值,其中的选择需要经验。您应该尽可能避免从面向对象的角度来解决问题,而应该考虑数据、函数、单子层。事实证明,这会让你在 Haskell 中走得更远。只有在少数应用程序中,面向对象是如此自然,值得将其压入语言中。


Sorry, this subject always drives me into strong-opinion rant mode...

&ddagger;These paranoia are partly motivated by the troubles of mutability, which don't arise in Haskell.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Haskell 中的面向对象编程 的相关文章

随机推荐