Live data from Hacker News

Inheritance was invented as a performance hack (2021)

catern.com

141–150 of 252 posts

Re: Inheritance was invented as a performance hack (2021)

#141

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'…

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.

Re: Inheritance was invented as a performance hack (2021)

#142

Earlier 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…

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();
    }

    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 the

1. 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)

#143
post #88

Earlier 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…

> And thats to say nothing of all the weird and wonderful bits of code which might modify that field when you aren't looking. Ugh.

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)

#144
post #36

Earlier 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.

We're all flavoured by our experience. You can for sure make a mess with flat C-style code that uses structs and global functions. But whenever I've seen a mess in C, its a sort of "lego on the floor" type of mess. Code is everywhere, but all the pieces are uniquely named and mostly self contained.

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)

#145
post #90

Earlier 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.

I think this is a lumpers/ splitters thing. The problem with a tree structure is that it assumes a very particular shape and you have to distort your understanding of the world to make that fit reality which has no such inherent pattern.

So, the taxonomy isn't useless, but, it's also never sufficient.

Re: Inheritance was invented as a performance hack (2021)

#146

Earlier 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.

I suspect that an experienced golang programmer could solve whatever abstraction problem you have using Go's tools of composition and interfaces. Chatgpt could probably get you started too, if you prompt it in the right way.

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)

#147

Earlier 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?

Why is your data object defined in multiple places in your codebase? And why aren't you using your IDE to change them all at once?

Re: Inheritance was invented as a performance hack (2021)

#148

Earlier 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…

One way you could fix this with composition is:

    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)

#149

Earlier 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…

I've seen such difficult to follow code in Java, and I complain about it every time. But having multiple inheritance isn't the cause (because Java doesn't have that). Nor does having multiple interfaces with default implementations have any real impact (at least in _any_ case I've seen).

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)

#150

Earlier 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…

Here's my take on implementing this in rust. I made a trait for fetching metadata, that can be implemented by Image, Video, Document, etc:

    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...

Post reply on HN