4 ms·
> Another PEP 351 world view is that tuples can serve as frozenlists; however, that view represents a Liskov violation (tuples don't support the same methods)
by zahlman 11mo ago
> Another PEP 351 world view is that tuples can serve as frozenlists; however,
that view represents a Liskov violation (tuples don't support the same
methods). This idea resurfaces and has be shot down again every few months.
... Well, yes; it doesn't support the methods for mutation. Thinking of ImmutableFoo as a subclass of Foo is never going to work. And, indeed, `set` and `frozenset` don't have an inheritance relationship.
I normally find Hettinger very insightful so this one is disappointing. But nobody's perfect, and we change over time (and so do the underlying conditions). I've felt like frozendict was missing for a long time, though. And really I think the language would have been better with a more formal concept of immutability (e.g. linking it more explicitly to hashability; having explicit recognition of "cache" attributes, ...), even if it didn't go the immutable-by-default route.
- pansa2 11mo ago> ImmutableFoo as a subclass of Foo is never going to work. And, indeed, `set` and `frozenset` don't have an inheritance relationship. Theoretically, could `set` be a subclass of `frozenset` (and `dict` of `frozendict`)? Do other languages take that approach? > linking [immutability] more explicitly to hashability AFAIK immutability and hashability are equivalent for the language's "core" types. Would it be possible to enforce that equivalence for user-defined types, given that mutability and the implementation of `__hash__` are entirely controlled by the programmer?
- chriswarbo 11mo ago> Theoretically, could `set` be a subclass of `frozenset` (and `dict` of `frozendict`)? At one extreme: sure, anything can be made a subclass of anything else, if we wanted to. At the other extreme: no, since Liskov substitution is an impossibly-high bar to reach; especially in a language that's as dynamic/loose as Python. For example, consider an expression like '"pop" in dir(mySet)'
- tremon 11mo ago> consider an expression like '"pop" in dir(mySet)' class frozenset: pass class set(frozenset): def pop(self, key): pass I don't see why hasattr(mySet, 'pop') should be a problem here?
- chriswarbo 11mo ago> I don't see why hasattr(mySet, 'pop') should be a problem here? I never said it's a problem (and I never said it's not!). I was specifically addressing two things: - The "theoretical" nature of the question I quoted (i.e. ignoring other aspects like subjectivity, practicality, convention, etc.) - The reasoning about "Liskov violation", which was quoted further up this thread. For context, here's Liskov's definition of their principle (from https://en.wikipedia.org/wiki/Liskov_substitution_principle https://en.wikipedia.org/wiki/Liskov_substitution_principle ): > Barbara Liskov and Jeannette Wing described the principle succinctly in a 1994 paper as follows:[1] > > Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T. My expression `"pop" in dir(mySet)` gives an explicit example of how `set` and `frozenset` are not subtypes of each other (regardless of how they're encoded in the language, with "subclasses" or whatever). In this case `ϕ(x)` would be a property like `'"pop" in dir(x)' = 'False'`, which holds for objects x of type frozenset. Yet it does not hold for objects y of type set. Your example of `hasattr(mySet, 'pop')` gives another property that would be violated. My point is that avoiding "Liskov violations" is ("theoretically") impossible, especially in Python (which allows programs to introspect/reflect on values, using facilities like 'dir', 'hasattr', etc.). (FYI I became rather jaded on the Liskov substitution principle after reading https://okmij.org/ftp/Computation/Subtyping https://okmij.org/ftp/Computation/Subtyping )
- tremon 11mo ago> > Subtype Requirement: Let ϕ(x) be a property provable about objects x of type T. Then ϕ(y) should be true for objects y of type S where S is a subtype of T. This says "if hasattr(parent, 'pop') == True then hasattr(child, 'pop') must be True". This is not violated in this case, since hasattr(parent, 'pop') is False. If you want to extend the above definition so that negative proofs concerning the parent should also hold true for the child, then subtyping becomes impossible since all parent and child types must be identical, by definition.
- kccqzy 11mo agoYes you could. Other languages do. See NSMutableSet and NSSet in Objective-C.
- kccqzy 11mo agoApple (or perhaps NeXT) has solved this problem already in Objective-C. Look at NSArray and NSMutableArray, or NSData and NSMutableData. It’s intuitive and Liskov-correct to make the mutable version a subclass of the immutable version. And it’s clearly wrong to have the subclass relationship the other way around. Given how dynamic Python is, such a subclass relationship need not be evident at the C level. You can totally make one class whose implementation is independent of another class a subclass of the other, using PEP 3119. This gives implementations complete flexibility in how to implement the class while retaining the ontological subclass relationship.
- tmp10423288442 11mo agoAnd, to the point of this proposal, `dict` and `frozendict` don't have an inheritance relationship either.
- immibis 11mo agoImmutableFoo can't be a subclass of Foo, since it loses the mutator methods. But nor can Foo be a subclass of ImmutableFoo, since it loses the axiom of immutability (e.g. thread-safety) that ImmutableFoo has. When you interpret Liskov substitution properly, it's very rare that anything Liskov-substitutes anything, making the entire property meaningless. So just do things based on what works best in the real world and aim for as much Liskov-substitution as is reasonable. Python is duck-typed anyway. It's a decent guiding principle - Set and ImmutableSet are more substitutable than Set and Map, so Set deriving from ImmutableSet makes more sense than Set deriving from Map. It's just not something you can ever actually achieve.
- FreakLegion 11mo ago> I've felt like frozendict was missing for a long time, though. Type the dict as a mapping when you want immutability: x: Mapping[int, int] = {1: 1} x[1] = 2 # Unsupported target for indexed assignment ("Mapping[int, int]"). The only problem I've seen with this is: y = {} y[x] = 0 # Mypy thinks this is fine. Mapping is hashable, after all! The issue here is less that dict isn't hashable than that Mapping is, though.
- zahlman 11mo agoThis is because the ABC system is defined such that MutableMapping is a subtype of Mapping. Which mostly makes sense, except that if we suppose there exist Mappings that aren't MutableMappings (such that it makes sense to recognize two separate concepts in the first place), then Mapping should be hashable, because immutable things generally should be hashable. Conceptually, making something mutable adds a bunch of mutation methods, but it also ought to take away hashing. So Liskov frowns regardless.
- FreakLegion 11mo agoIt really doesn't make sense for there to be an inheritance relationship between Mapping and MutableMapping if Mapping is immutable (it isn't, of course), but the weirder part is still just that the typing machinery is cool with unhashable key types like: x: dict[list, int] = {} x[[1, 2, 3]] = 0