Earlier quoted context omitted.
Yeah, I have seen things like you describe. But I have also seen the same code, copy-pasted a dozen times throughout a codebase and modified over years. That is a much worse situation; the links between the abstractions still exist without the inheritance, but now they are untraceable. At least with inheritance there are links between the methods and classes for you to follow. Without it, you've got to crawl the enti…
> OOP is easily the lesser of the two evils; without it, you're doomed to violate DRY in ways that will make your project unmaintainable. Inheritance isn't the only way to avoid duplicating code. Composition works great - and it results in much more maintainable code. Rust, for example, doesn't have class based inheritance at all. And the principle of DRY is maintained in everything I've made in it. And everything I'…
Inheritance was invented as a performance hack (2021)
141–150 of 252 posts
Re: Inheritance was invented as a performance hack (2021)
#142Earlier quoted context omitted.
But if there's a lot of classes that implement the same thing, then not duplicating code makes sense. And saying "it's an implementation detail" leads to having the same code in a bunch of different classes. It feels very similar to the idea of default implementations to me; when the implementation will be the same everywhere, it makes sense to have it in one place.
So to be clear about your example: You have a whole lot of different - totally distinct - types of things, which all need to have the same logic to cache HTTP requests? Can you give some examples of these different types you're creating? Why do you have lots of distinct types that need exactly the same caching logic? It sounds like you could solve that problem in a lot of different ways. For example, you could make a…
public interface MetadataSource {
Metadata metadata = null;
default Metadata getMetadata() {
if (metadata == null) {
metadata = fetchMetadata();
}
return metadata;
}
// This can be relatively costly
Metadata fetchMetadata();
}
public class Image implements MetadataSource {
public Metadata fetchMetadata() {
// goes to externally hosted image to fetch metadata
}
}
public class Video implements MetadataSource {
public Metadata fetchMetadata() {
// goes to video hosting service to get metadata
}
}
public class Document implements MetadataSource {
public Metadata fetchMetadata() {
// goes to database to fetch metadata
}
}
Each of the above have completely different ways to fetch their metadata (ex, Title and Creator), and of them has different characteristics related to the cost of getting that data. So, by default, we want the interface to cache the result so that the1. The thing that _has_ the metadata only needs to know how to fetch it when it's asked for (implementation of fetchMetadata), and it doesn't need to worry about the cost of doing so (within limits of course)
2. The things that _use_ the metadata only need to know how to ask for it (getMetadata) and can assume it has minimal cost.
3. Neither one of those needs to know anything about it being cached.
I had a case recently where I needed to check "does this have metadata available" separate from "what is the metadata". And fetching it twice would add load.
Re: Inheritance was invented as a performance hack (2021)
#143Earlier quoted context omitted.
And Rust's traits can sort-of inherit from each other.
I'm fine with trait inheritance. (If you want to call it that - its maybe better to describe it as trait preconditions.) I'm fine with it because trait inheritance doesn't increase code complexity in the same way C++ / Java class inheritance does. If you call foo.bar(), its usually pretty obvious which function is being called. And you only ever have to look in one place to see all the fields of a struct. In C++, its…
Agreed, mutation tends to make everything worse and definitely more complicated.
Mutation is a powerful technique, but needs to be treated with care. Haskell and Rust (and Erlang) amongst others have some interesting approaches for how to recognise the danger of mutations, but still harness their upsides.
Haskell even has quite a few different approaches to choose from, or to mix-and-match.
Re: Inheritance was invented as a performance hack (2021)
#144Earlier quoted context omitted.
> The bottom line is, no one ever really used inheritance that much anyway If you think that, you have no idea how much horrible code is out there. Especially in enterprise land, where deadlines are set by people who get paid by the hour. I once worked on a java project which had a method - call a method - call a method - call a method and so on. Usually, the calls were via some abstract interface with a single imple…
> Usually, the calls were via some abstract interface with a single implementor What's described here is over-generic code, instead of KISS and just keeping an eye on extensibility instead of generalizing ahead of time. This can happen in any paradigm.
Classes - and class hierarchies - really let you go to town. I've seen codebases that seem totally impossible to get your head around. The best is when you have 18 classes which all implicitly or explicitly depend on each other. In that case, just starting the program up requires an insane, fragile dance where lots of objects need to be initialized in just the perfect order, otherwise something hits a null pointer exception in its initialization code. You reorder two lines in a constructor somewhere and something on the other side of your codebase breaks, and you have no idea why.
For some reason I've never seen anyone make that kind of mess just using composition. Maybe I just haven't been around long enough.
Re: Inheritance was invented as a performance hack (2021)
#145Earlier quoted context omitted.
Botanical trees appearing in nature don't make them "the fundamental truth of things". And in what way are trees the basis of human society? Thats such a strange claim. Are you talking about family trees? Because they're actually directed acyclic graphs. Even if you want to claim that trees are a common data structure, that doesn't mean they're appropriate in any specific case. Should we therefore arrange all website…
"Taxonomies are entirely and completely worthless." Hard disagree. Knowing that AES and Twofish are block ciphers is useful when dealing with cryptography. Many categories of algorithms and objects are naturally taxonomic. Even HTML+CSS has (messy) inheritance.
So, the taxonomy isn't useless, but, it's also never sufficient.
Re: Inheritance was invented as a performance hack (2021)
#146Earlier quoted context omitted.
> OOP is easily the lesser of the two evils; without it, you're doomed to violate DRY in ways that will make your project unmaintainable. Inheritance isn't the only way to avoid duplicating code. Composition works great - and it results in much more maintainable code. Rust, for example, doesn't have class based inheritance at all. And the principle of DRY is maintained in everything I've made in it. And everything I'…
I've experimented with GoLang and found the lack of inheritance to be crippling for cases when I want to set a pattern in the code that is to be easily used by other devs with minimal training and a shared definition of behavior. That said, I truly think some mix of inheritance and composition is probably best to avoid the situations we're describing.
Generally, don't treat Go as if its some bad imitation of C++ or Java. Its a different language. Like all languages, idiomatic Go is its own thing. It looks different to idiomatic Ruby or Javascript or C++ or Perl.
I think of programming languages kind of like pieces of wood. Each language has its own "grain" that you need to follow when you work. If you try and force any programming language into acting like its something else, you're going against the grain of the language. You'll need to work 10x harder to get anywhere if you try to work like that. Spend more time learning.
Re: Inheritance was invented as a performance hack (2021)
#147Earlier quoted context omitted.
I think you have the consequences of AI exactly backwards. AI provides virtual headcount and will vastly increase the ability of small teams to manage sprawling codebases. LLM context lengths are already on the order of millions of tokens. It takes a human days of work to come to grips with a codebase an LLM can grok in two seconds. The cost of working with code is much lower with LLMs than with humans and it's falli…
So if you've got a data object, defined in multiple places in a sprawling codebase, that you want to change, are you going to trust the LLM to find them all, and not miss a single one?
Re: Inheritance was invented as a performance hack (2021)
#148Earlier quoted context omitted.
So to be clear about your example: You have a whole lot of different - totally distinct - types of things, which all need to have the same logic to cache HTTP requests? Can you give some examples of these different types you're creating? Why do you have lots of distinct types that need exactly the same caching logic? It sounds like you could solve that problem in a lot of different ways. For example, you could make a…
From a very modified version of something I was working on recently, but with the stuff I couldn't do actually done here (and non-functionality code because of that, but is shows the idea) public interface MetadataSource { Metadata metadata = null; default Metadata getMetadata() { if (metadata == null) { metadata = fetchMetadata(); } return metadata; } // This can be relatively costly Metadata fetchMetadata(); } publ…
class CachedMetadataSource implements MetadataSource {
CachedMetadataSource(MetadataSource uncachedSource) {}
Metadata getMetadata() {
if (metadata == null) {
metadata = uncachedSource.getMetadata();
}
return metadata;
}
}Re: Inheritance was invented as a performance hack (2021)
#149Earlier quoted context omitted.
> Even if they do, what happens when both classes implement a method or field with the same name? It's done in Java with interfaces with default implementations, and the world hasn't imploded. It just doesn't seem like that big of a problem.
> It just doesn't seem like that big of a problem. It really, really depends on the codebase. There are absolute mammoth tire fire codebases out there - particularly in "enterprise code". These are often made up of insane hierarchies of classes which in practice do nothing but obscure where any of the actual logic lives for your program. AbstractFactoryBuilderImpl. Wild goose chases where you need some bizzaire and f…
The only real difference I see between multiple inheritance and multiple interfaces with default implementations is constructors. And they can be handled in the same way as default implementations; requiring specific usage/ordering.
Re: Inheritance was invented as a performance hack (2021)
#150Earlier quoted context omitted.
So to be clear about your example: You have a whole lot of different - totally distinct - types of things, which all need to have the same logic to cache HTTP requests? Can you give some examples of these different types you're creating? Why do you have lots of distinct types that need exactly the same caching logic? It sounds like you could solve that problem in a lot of different ways. For example, you could make a…
From a very modified version of something I was working on recently, but with the stuff I couldn't do actually done here (and non-functionality code because of that, but is shows the idea) public interface MetadataSource { Metadata metadata = null; default Metadata getMetadata() { if (metadata == null) { metadata = fetchMetadata(); } return metadata; } // This can be relatively costly Metadata fetchMetadata(); } publ…
trait MetadataSource {
fn fetch_metadata(&self) -> Metadata;
}
impl MetadataSource for Image { ... }
impl MetadataSource for Video { ... }
impl MetadataSource for Document { ... }
And a separate object which stores an image / video / document alongside its cached metadata: struct ThingWithMetadata {
obj: T, // Assuming you need to store this too?
metadata: Option
}
impl ThingWithMetadata {
fn get_metadata(&self) -> &Metadata {
if self.metadata.is_none() {
self.metadata = Some(self.obj.fetch_metadata());
}
self.metadata.as_ref().unwrap()
}
}
Its not the most beautiful thing in the world, but it works. And it'd be easy enough to add more methods, behaviour and state to those metadata sources if you want. (Eg if you want Image to actually load / store an image or something.)In this case, it might be even simpler if you made Image / Video / Document into an enum. Then fetch_metadata could be a regular function with a match expression (switch statement).
If you want to be tricky, you could even make struct ThingWithMetadata also implement MetadataSource. If you do that, you can mix and match cached and uncached metadata sources without the consumer needing to know the difference.
https://play.rust-lang.org/?version=stable&mode=debug&editio...