On Jul 11, 2023, at 13:42, Larry Garfield <la...@garfieldtech.com> wrote:
> 
> On Tue, Jul 11, 2023, at 5:28 PM, Robert Landers wrote:
>> 
>> IMHO, using a shared base class reflects the inheritance better
>> because they are siblings (at least these appear to be logical
>> siblings out of context, IMHO) and should look like siblings in a
>> class diagram. Their current dis-jointed-ness strikes me as odd and
>> the usage of traits in this way strikes me as an anti-pattern. But I
>> came from a PHP world where traits were nearly forbidden or used very
>> sparingly.
> 
> Traits are a surgical tool with a narrow set of uses, and systems that 
> over-use them cause problems.  (You know which framework you are...)  But 
> this is one of their main valid uses.
> 
> In context, the classes are not siblings, and it would be incorrect for them 
> to extent from a base class.  They just both happen to implement the same 
> *interface*, but that the same as having the same "is a special case of" 
> relationship with a base class.
> 
> Having a common base class instead would, as noted before, mean as soon as I 
> added a third "carries" option, I'd have to add four more base classes to 
> cover all combinations.  It quickly gets absurd, and those base classes have 
> no valid type usage; they're purely there as an alternative to copy-paste.
> 
> Using inheritance as an alternative to copy-paste is the wrong way(tm) to do 
> it.  Both inheritance and copy-paste.  Freshman CS classes still love to talk 
> about inheritance as a great thing for code reuse but... it's really not, and 
> many of the 21st century languages have been drifting away from that.
> 
> Traits/default-method-interfaces are a better alternative that doesn't 
> conflate "copy-paste avoidance" with "is a special case of."  Honestly, I 
> almost never use class inheritance in my greenfield code these days.  


I'm stating my +1 for this feature (though I can't vote since I lack vote 
karma), and also want to point that this is a feature in Swift as well, and 
used extensively through its standard library. (The actual mechanics are 
somewhat different, and significantly more powerful, both for good and ill. I'm 
handwaving a bit to not get bogged down in details that don't/can't matter for 
PHP.)


A basic example in Swift's standard library is the Equatable and Comparable 
interfaces. Both have partial implementations provided. Between the two, if you 
implement the == and < operations, the stdlib provides !=, <=, >, and >= 
automatically, but you can override them if desired by providing an explicit 
implementation. This is used pretty much everywhere; most of the builtin types 
(like Int, Float, String, and Array, which otherwise have no relation to each 
other and definitely don't inherit from some base type) implement Equatable and 
Comparable.

Another example is the Sequence interface, which comes with a set of default 
implementations for standard sequence algorithms, such as map, first, min/max, 
contains, etc. This allows all types of sequences to have the functionality 
expected of a sequence without needing to redundantly implement basic 
operations (unless desired) or participate in inheritance.

This clear distinction between what a type _is_ (its type and inheritance tree) 
and what a type _can do_ (the interfaces it implements) is really important 
when you start piling on the interfaces. Swift's standard library comes with a 
laundry list of interfaces that provide partial or complete default 
implementations, and it's entirely reasonable to combine these together. 
Consider, for example, a type that implemented each of Sequence, Collection, 
Encodable, Decodable, Equatable, and Hashable (such as Array or Dictionary). 
Creating a sane inheritance tree for that would be difficult. (And that's 
before considering that an array or dictionary should itself be 
Encodable/Decodable/Equatable/Hashable if its elements (and keys) have those 
properties. In Swift, the implementation for Array and Dictionary is largely 
the details of managing data storage. Useful algorithms get automatically 
inherited from default implementations from their many interfaces without 
needing a common parent class for both arrays and dictionaries, which are both 
structs which can't have inheritance anyway.)


I'll also point out that (in both PHP and Swift) enums can't participate in 
inheritance, but can implement interfaces. If one wanted to provide a default 
implementation for an interface used an enum, an abstract class isn't an option 
at all.


Anecdotally, outside of framework-mandated inheritance, the vast majority of my 
types in Swift are structs, enums, and classes with no inheritance, and many 
have interfaces that have some default implementation provided. Most of my PHP 
classes that have inheritance would be better served with interfaces + default 
implementations; they use inheritance only because it is currently the least 
bad way to make them work. Inheritance doesn't really capture the relationship 
between them (which is that there is none, save for their shared interface).

-John

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to