Do I understand correctly that Liskov Substitution Principle cannot be observed in languages where objects can inspect themselves, like what is usual in duck typed languages?
For example, in Ruby, if a class B
inherits from a class A
, then for every object x
of A
, x.class
is going to return A
, but if x
is an object of B
, x.class
is not going to return A
.
Here is a statement of LSP:
Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T.
So in Ruby, for example,
class T; end
class S < T; end
violate LSP in this form, as witnessed by the property q(x) = x.class.name == 'T'
Addition. If the answer is "yes" (LSP incompatible with introspection), then my other question would be: is there some modified "weak" form of LSP which can possibly hold for a dynamic language, possibly under some additional conditions and with only special types of properties.
Update. For reference, here is another formulation of LSP that I've found on the web:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
And another:
If S is a declared subtype of T, objects of type S should behave as objects of type T are expected to behave, if they are treated as objects of type T.
The last one is annotated with:
Note that the LSP is all about expected behaviour of objects. One can only follow the LSP if one is clear about what the expected behaviour of objects is.
This seems to be weaker than the original one, and might be possible to observe, but I would like to see it formalized, in particular explained who decides what the expected behavior is.
Is then LSP not a property of a pair of classes in a programming language, but of a pair of classes together with a given set of properties, satisfied by the ancestor class? Practically, would this mean that to construct a subclass (descendant class) respecting LSP, all possible uses of the ancestor class have to be known? According to LSP, the ancestor class is supposed to be replaceable with any descendant class, right?
Update. I have already accepted the answer, but i would like to add one more concrete example from Ruby to illustrate the question. In Ruby, each class is a module in the sense that Class
class is a descendant of Module
class. However:
class C; end
C.is_a?(Module) # => true
C.class # => Class
Class.superclass # => Module
module M; end
M.class # => Module
o = Object.new
o.extend(M) # ok
o.extend(C) # => TypeError: wrong argument type Class (expected Module)
Best Answer
Here's the actual principle:
And the excellent wikipedia summary:
And some relevant quotes from the paper:
So on to the question:
No.
A.class
returns a class.B.class
returns a class.Since you can make the same call on the more specific type and get a compatible result, LSP holds. The issue is that with dynamic languages, you can still call things on the result expecting them to be there.
But let's consider a statically, structural (duck) typed language. In this case,
A.class
would return a type with a constraint that it must beA
or a subtype ofA
. This provides the static guarantee that any subtype ofA
must provide a methodT.class
whose result is a type that satisfies that constraint.This provides a stronger assertion that LSP holds in languages that support duck typing, and that any violation of LSP in something like Ruby occurs more due to normal dynamic misuse than a language design incompatibility.