Skip to content

Commit

Permalink
Improve the wording in the 'pattern' docs a bit
Browse files Browse the repository at this point in the history
  • Loading branch information
patrick-kidger committed Oct 29, 2024
1 parent 689c35a commit f009504
Showing 1 changed file with 6 additions and 6 deletions.
12 changes: 6 additions & 6 deletions docs/pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ The following is a useful design pattern. It's not mandatory, but it does come r
(a) abstract (it can be subclassed, but not instantiated); or
(b) final (it can be instantiated, but not subclassed).

Unless they're abstract, then methods and attributes must never be overridden. Once they've been implemented, that's it.
Only abstractmethods and abstract attributes can be overridden. (So once they've been implemented, then a subclass may not override them.)

The `__init__` method, and all dataclass fields, must all be defined in one class. No defining fields in multiple parts of a class hierarchy.
The `__init__` method, and all dataclass fields, must all be defined in one class. (No defining fields in multiple parts of a class hierarchy.)

Collectively, these rules serve to avoid all the worst ways in which you might spaghetti your code. Abstract classes are used to define interfaces or partial implementations. Final classes are what get passed around at runtime. No overriding (of methods or of non-abstract classes) means there are no surprises with exactly what is being called. Keeping all fields together means that initialisation is readable.
Collectively, these rules serve to avoid all the worst ways in which you might spaghetti your code. Abstract classes are used to define interfaces or partial implementations. Final classes are what get passed around at runtime. No overriding (either of methods or of non-abstract classes) means there are no surprises with exactly what is being called. Keeping all fields together means that initialisation is readable.

Equinox will enforce these rules when subclassing with `strict=True`, e.g.
```python
Expand All @@ -32,8 +32,8 @@ class Bar(Foo, strict=True):
Some quick FAQs:

- If we want to subclass to override just one method, just to tweak it a little bit -- this can be done by wrapping the original class, not subclassing it.
- In practice we typically define all fields, and the `__init__` method, on the final class that we instantiate. Every so often we could do them on an abstract class though, in which case the final subclasses will only be implementing methods.
- You should never use the `hasattr` builtin. You probably meant to declare this attribute/method on an abstract base class.
- In practice we typically define all fields, and the `__init__` method, on the final class that we instantiate. (In principle we could do it in an abstract class if we wanted, and let its concrete subclasses still provide implementations of a few methods, it's just rare to need that.)
- You should never use the `hasattr` builtin. You should declare this attribute or method on an abstract base class.
- To access an attribute on an abstract class, it can be declared using [`equinox.AbstractVar`][]; the eventual final subclass must provide this as a field or property.

And for the CS nerds:
Expand Down Expand Up @@ -242,7 +242,7 @@ B() # AA.__init__ is not called.
```
In this case `B()` calls `A.__init__` and this then fails to call `AA.__init__`. Co-operative multiple inheritance only works if everyone, well, co-operates.

Even if everyone wants to do their best, there is another issue. When writing `super().__init__`, it isn't actually know what method is being called -- as above, `super()` could be pointing at almost any class at all. This actually means that it's not possible to know what arguments to pass to `super().__init__`! "Only use keyword arguments" is the closest to a resolution that this issue has, and it's still fragile.
Even if everyone wants to do their best, there is another issue. When writing `super().__init__`, it isn't actually known what method is being called -- as above, `super()` could be pointing at almost any class at all. This actually means that it's not possible to know what arguments to pass to `super().__init__`! "Only use keyword arguments" is the closest to a resolution that this issue has, and it's still fragile.

In contrast, our no-overriding and abstract-or-final rules means that we never come across this scenario. We always know precisely what is being called.

Expand Down

0 comments on commit f009504

Please sign in to comment.