例如,在Java中,通用的基类Object定义了方法boolean equals(Object obj),因此被所有Java类继承,并允许进行如下比较:
String hello = "Hello";
String world = "World";
Integer three = 3;
Boolean a = hello.equals(world);
Boolean b = world.equals("World");
Boolean c = three.equals(5);
不幸的是,由于 Liskov 替换原则,在 Java 中,子类不能比基类更加限制接受哪些方法参数,因此 Java 也允许一些毫无意义的比较,这些比较永远不可能为真(并且可能导致非常微妙的错误):
Boolean d = "Hello".equals(5);
Boolean e = three.equals(hello);
另一个不幸的副作用是,正如Josh Bloch在很长时间之前在Effective Java中指出的那样,在存在子类型的情况下基本上不可能根据其合同正确实现equals
方法(如果在子类中引入了附加字段,则实现将违反合同的对称性和/或传递性要求)。
现在,Haskell的Eq
类型类是完全不同的东西:
Prelude> data Person = Person { firstName :: String, lastName :: String } deriving (Eq)
Prelude> joshua = Person { firstName = "Joshua", lastName = "Bloch"}
Prelude> james = Person { firstName = "James", lastName = "Gosling"}
Prelude> james == james
True
Prelude> james == joshua
False
Prelude> james /= joshua
True
这里,不同类型对象之间的比较会被拒绝并报错:
Prelude> data PersonPlusAge = PersonPlusAge { firstName :: String, lastName :: String, age :: Int } deriving (Eq)
Prelude> james65 = PersonPlusAge { firstName = "James", lastName = "Gosling", age = 65}
Prelude> james65 == james65
True
Prelude> james65 == james
<interactive>:49:12: error:
• Couldn't match expected type ‘PersonPlusAge’
with actual type ‘Person’
• In the second argument of ‘(==)’, namely ‘james’
In the expression: james65 == james
In an equation for ‘it’: it = james65 == james
Prelude>
虽然这个错误比Java处理相等性的方式更直观,但它似乎表明像Eq
这样的类型类可以对其子类型的方法允许的参数类型进行更严格的限制。在我看来,这似乎违反了LSP。
我的理解是,Haskell不支持面向对象意义上的“子类型”,但这是否意味着Liskov替换原则不适用?
class Eq a => Ord a
表示如果一个类型在Ord
中,则它也必须在Eq
中,因此Ord
是Eq
的子集。(箭头=>
是逆推符号。) - Jon Purdy