【问题标题】:If Car is a subtype of Vehicle, why is Vehicle->void considered a subtype of Car->void?如果 Car 是 Vehicle 的子类型,为什么 Vehicle->void 被认为是 Car->void 的子类型?
【发布时间】:2019-03-17 11:17:32
【问题描述】:

阅读a fundamental paper关于继承,我无法理解下面显示的推理。显然它是正确的,因为逆变确实有效,我只是想了解其中的原因。 首先证明:

  • 如果函数返回 Car,则它是返回 Vehicle 的函数的子类型。那是因为所有的车辆也是汽车。 (返回类型的协方差)。
  • 如果一个函数采用 Vehicle,那么它就是一个采用 Car 的函数的子类型,因为“一般来说,每个函数在车辆上也是一个功能 汽车。
    我无法理解这种反转的解释,所以我在下面展示这个:

我的幼稚解读:

由于所有车辆上的功能都适用于所有汽车,因此采用 Vehicle 的函数是采用 Car 的函数的子类型,因为 Vehlice 的功能集更小 - Car 可以有更多的功能。

【问题讨论】:

  • 所有汽车都是车辆,但并非所有车辆都是汽车 (C

标签: inheritance functional-programming language-agnostic covariance contravariance


【解决方案1】:

您的问题是关于函数的协变和逆变,我认为如果我们将论文中的一些与语言无关的符号关系映射到实际代码,这将有助于您的理解。在 C++ 中,这里讨论的函数是:

int GetSpeedOf(Vehicle vehicle);

子类型必须按照Liskov-substitution来理解;如果一个函数需要任何类型的动物,你可以给它一只猫,一切都应该正常工作,但反之则不然;需要猫的功能无法对任何类型的动物起作用。

假设您了解可以将 Car 传递给 GetSpeedOf 函数,现在我们将讨论更复杂的函数接受函数的情况,这会带来逆变。

下面的 CarWrapper 有一个私有 Car,它将使用从外部提供的函数对其进行处理。该功能必须适用于汽车。因此,如果您提供一个更普遍适用于所有车辆的功能,那很好。

#include <functional>

class Vehicle { };
class Car : public Vehicle { };

class CarWrapper
{
    Car car;
    typedef std::function<auto(Car) -> void> CarCallback;
    CarCallback functionPtr;

public:
    void PassCallback(CarCallback cb)
    {
        functionPtr = cb;
    }

    void DoStuff()
    {
        functionPtr(car);
    }
};

void Works(Vehicle v){}

int main() {
    CarWrapper b;
    b.PassCallback(&Works);
}

所以,PassCallback 函数需要 CarCallback 类型(论文中的“Car -> void”),但我们给它一个子类型“Vehicle -> void”,因为“&Works”的类型实际上是std::function&lt;auto(Vehicle) -&gt; void&gt; .因此“如果一个函数采用 Vehicle,那么它就是采用 Car 的函数的子类型”。这是可能的,因为“Works”函数将执行的所有操作也必须在实际传入的内容(汽车)上是可能的。

这种情况的一个用例是,Works 函数也可以传递给 BoatWrapper 类,该类需要在船上运行的函数。我们可以给出该函数类型的子类型“Vehicle -> void”,知道实际传递的函数中的所有操作也必须在传递给它的 Boat 上可用,因为 Boat 是 Vehicle 的子类型,而我们的实际函数只使用要操作的参数的更一般的 Vehicle'ness。

另一方面,协方差作用于返回类型;如果 CarWrapper 类需要一个为我们生成 Car 的回调,我们将无法传入 Vehicle 生成函数,因为这样 CarWrapper 将无法以特定于汽车的方式使用结果。

如果我们有一个期望车辆发电机的函数,我们可以给它一个汽车发电机或船发电机;因此 (void -> Car) 是 (void -> Vehicle) 的子类型,当且仅当 Car 是 Vehicle 的子类型。

协方差意味着子类型关系保持相同的方向, 所以我们可以继续坚持另一个功能应用程序,仍然“汽车端”将是“车辆端”的子类型,即:

Car is a subtype of Vehicle means that:
(void -> Car) is a subtype of (void -> Vehicle) - as in the code sample above
(void -> (void -> Car)) is a subtype of (void -> (void -> Vehicle))
(void -> (void -> (void -> Car))) is a subtype of (void -> (void -> (void -> Vehicle)))

这意味着,如果我们期望一个 VehicleFactoryFactoryFactory,我们应该在给定一个 CarFactoryFactoryFactory 时感到满意:

#include <functional>

class Vehicle { };
class Car : public Vehicle { };

typedef std::function<auto() -> Vehicle> VehicleFactory;
typedef std::function<auto() -> VehicleFactory> VehicleFactoryFactory;
typedef std::function<auto() -> VehicleFactoryFactory> VehicleFactoryFactoryFactory;

void GiveMeAFactory(VehicleFactoryFactoryFactory factory)
{
    Vehicle theVehicle = factory()()();
}

typedef std::function<auto() -> Car> CarFactory;
typedef std::function<auto() -> CarFactory> CarFactoryFactory;
typedef std::function<auto() -> CarFactoryFactory> CarFactoryFactoryFactory;

Car ActualCarCreateFunc() { return Car(); }
CarFactory CarFactoryCreateFunc() { return &ActualCarCreateFunc; }
CarFactoryFactory CarFactoryFactoryCreateFunc() { return &CarFactoryCreateFunc; }

int main() {
    GiveMeAFactory(&CarFactoryFactoryCreateFunc);
}

通过参数类型的逆变,关系与每个函数应用程序反转。

Car is a subtype of Vehicle means that:
(Vehicle -> void) is a subtype of (Car -> void)
((Car -> void) -> void) is a subtype of ((Vehicle -> void) -> void)
(((Vehicle -> void) -> void) -> void) is a subtype of (((Car -> void) -> void) -> void)

在逆变的情况下,很难直观地理解这一点。这就是为什么我的 CarWrapper 仅尝试针对规则的单个应用来解释它,而 CarFactory 示例包含协方差的三个应用。

【讨论】:

  • 谢谢,它确实帮助了我,但如果你能详细说明 Void(Vehicle) 的那部分是 void(Car) 的子类型,我将不胜感激。理想情况下,就像我原来的问题下的评论一样简单。
  • 我认为你的最后一段有错误 - Car->Void 不是 Vehicle->void 的子类型。
  • 正确;我不得不在纸上画出来,看看逆变的多重应用应该如何工作。希望现在我将它放在多行上会更清楚。在第一个代码示例之后,我还添加了一些更简单的英语。希望如果您考虑一下这些函数会包含哪些内容,您会理解为什么当 PassCallback 的签名请求实际在 Cars 上运行的函数时,Works 函数是有效的直觉是有意义的。
  • 非常感谢。我添加了自己的答案(正如我的一位同事用简单的话向我解释的那样),但我当然会留下您的答案,因为它提供了很多细节和实施示例。
猜你喜欢
  • 2022-10-17
  • 2022-01-06
  • 2020-01-14
  • 2012-05-03
  • 2021-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多