Value-Oriented Programming (2018)
matt.diephouse.com
matt.diephouse.com
A lot of this stuff came out of the Simula branch of OOP. It's the kind of stuff that C++, Java, C#, etc was modelled around. Objects are an encapsulation of data along with their operations.
The other way to go is more where the Smalltalk guys went. In this branch of OOP, what's important are the messages. Data is abstracted out and your goal is to refer to it as little as possible. Instead messages are passed around between objects and the objects are responsible for dealing with the messy business of handling the actual data. When you are designing you look for the message flow and then build objects that handle the messages.
When one says "Start writing an interface, not a class", I think what is meant is that you should concentrate on the messages that the objects need to react to, not the data within the classes. So you are designing a protocol, not an E-R diagram.
There are people who are adamant that only one of these two approaches will result in "good code". Often there is a considerable amount of derision for someone who believes in the opposite approach. The truth probably lies somewhere in between IMHO. There are advantages and disadvantages. When doing a data centric approach, it's very easy to understand the state that you are capturing. But it's also very easy to let the details of the data dominate and to create unnecessarily complex code dealing with that data. Often you end up with god objects and poor decomposition of behaviour. On the other hand, if you go for the protocol approach, often you get designs where the state is almost impossible to figure out and where you have "big bags of functions" because it is convenient. It is difficult to maintain the data abstractions and often the data details leak out, pretty much destroying your design.
Design is super difficult and requires a lot of experience of doing it. Different schools of thought and descriptions of approaches are helpful to a certain degree, but the best is to write a lot of code and get feedback from people with experience. My biggest piece of advice for people who are near the beginning of their career (i.e. first 10 years or so) is to try to work on an project (likely your own side project) for at least 5 to 10 years so that you can see it implode under its own weight. The biggest mistake I see developers make is assuming that things that appear to work for a year or two will continue to work as the code base ages. Especially I find people who have formulaic approaches to design often break the design badly in the long term (there are some 3 letter abbreviations that I could splash out if I wanted to pick a fight ;-) ) Unfortunately, these people rarely work on a project for more than a year or two, so you get what I call "drive by designings" -- just shoot it to pieces and drive on.
Drawing a Class Diagram of data fields was indeed the only way we learned ~20 years ago. But now that you mention it I find myself slipping into the other model a lot more lately. It's an especially natural fit for APIs.
This reminds me a bit o the "sans IO" model for writing network protocol implementations: https://sans-io.readthedocs.io/ By having your HTTP protocol implementation accept a value input "These bytes were received" and return a value output "Please send these bytes" instead of directly calling read() and write() methods from an interface, you can write an asynchronous upper layer API using your favorite async stack as easily as a synchronous one - you're no longer baking in the idea that the library handles making the operations happen and therefore sometimes blocks in a read. (And the lower-level library is also testable without mocks, etc.)
When I was writing a lot of Swfit this approach was substantially more banger than just being an interface, but it had a caveat-- your structs needed to be less than like four members (maybe three?) and if so Swift would just use the stack without having to do a bunch of heap allocations like with a class (or even a large struct). That is to say, the compiler and runtime had a trick this sort of pattern unlocked. I'd even guess now there's something extra going on under-the-hood by the Siwft compiler to make this style of programming moar gooder... but I'm super out of date with my Swift knowledge.
It's likely the Set<Path> will use heap allocation internally (there isn't really a way around that), but it's only holding a small value type.
Besides, this wasn't a streaming interface before this change, either. In the first version, all elements were held in an array already.
So similar compositional techniques to Swift protocols would be typeclasses in Haskell, traits in Scala, module interfaces in ML languages (which have to be used at the module-level via functors instead of at the value-level like the other examples), etc.
Edit: I should have read to the end. I stopped after reading a few paragraphs of what looked like a basic interface vs inheritance discussion. Turns out, at the mid-point, he introduces an "alternative" to that approach, which is indeed more or less functional programming. As much as I like functional programming, (his "Value-Oriented-Programming"), interfaces are often quite useful when programming in the large.
Edit: Yeah, you're right. I read to the end and that's pretty much functional programming as you suggested. I'm not convinced it's always better than using interfaces. Like most things, I think it depends. For example, particularly when programming "in-the-large", I think being able to package up different contexts (like a TestRenderer) into different protocol implementations is often an advantage of that approach, not a weakness, as he seems to imply.