哈斯克尔的类型系统是否遵循里氏替换原理?
我来自Java背景,我试图围绕Haskell的类型系统来思考。在Java世界中,Liskov替换原则是基本规则之一,我试图理解这是否(如果是这样,如何)这是一个也适用于Haskell的概念(请原谅我对Haskell的有限理解,我希望这个问题甚至有意义)。
例如,在Java中,公共基类定义了所有Java类继承的方法,并允许进行如下比较:Object
boolean equals(Object obj)
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处理相等的方式更有意义,但它似乎确实表明,像这样的类型类在允许子类型方法的参数类型方面可以更具限制性。在我看来,这似乎违反了LSP。Eq
我的理解是,Haskell不支持面向对象意义上的“子类型化”,但这是否也意味着Liskov替换原则不适用?