Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

"Composition" is a word that can mean several things, and without having read the original source I never really understood which version they mean. As a rule, I've always viewed "composition" as "gluing together things that don't know necessarily know about each other", and that definition works well enough, but that doesn't necessarily eliminate inheritance.

So then I start thinking in less-useful, more abstract definitions, like "inheritance is vertical, composition is horizontal", but of course that doesn't really mean anything.

And at some point, it seems like I just end up defining "composition" to mean "gluing together in a way that's not inheritance". Again, not really a useful definition.



I find the Monoid/Semigroup typeclass pretty concisely captures what is generally meant by "composition" in the minimal sense.

> As a rule, I've always viewed "composition" as "gluing together things that don't know necessarily know about each other"

The extension to this definition given the context of Monoids would be "combining two things of the same type such that they produce a new thing of the same type". The most trivial example of this is adding integers, but a more practical example is function composition where two functions can be combined to create a new function. You can also think of an abstraction that let's you combine two web components to create a new one, combining two AI agents to make a new one, etc.

> "inheritance is vertical, composition is horizontal", but of course that doesn't really mean anything.

This can actually be clearly defined, what you're hinting at is the distinction between sum types and product types. The latter of which describes inheritance. The problem with restricting yourself to only product types is that you can only add things to an existing thing, but in real life that rarely makes sense, and you will find yourself backed into a corner. Sum types let you have much more flexibility, which in turn make it easier to implement truly composable systems.


I actually knew most of that (I've done a lot of Haskell). I don't really disagree with what you said, but I feel like like you eliminate a lot of stuff that people would consider "composition" but aren't as easily classified in happy categories.

For example, a channel-based system like what Go or Clojure has; to me that is pretty clearly "composition", but I'm not 100% sure how you'd fully express something like that with categories; you could use something like a continuation monad but I think that loses a bit because the actual "channel" object has separate intrinsic value.

In Clojure, there's a "compose" function `comp` [1], which is regular `f(g(x))` composition, but lets suppose instead I had functions `f` and `g` running in separate threads and they synchronize on a channel (using core.async)? Is that still composition? There are two different things that can result in a very similar output, and both of which are considered by some to be composition. So which one of these should I "prefer" instead of inheritance?

Of course this is the realm of Pi Calculus or CSP if you want to go into theory, but I'm saying that I don't think that there's a "one definition to rule them all" for composition.

[1] https://clojuredocs.org/clojure.core/comp


I think there's still a category theoretic expression of this, but it's not necessarily easy to capture in language type systems.

The notion of `f` producing a lazy sequence of values, `g` consuming them, and possibly that construct getting built up into some closed set of structures - (e.g. sequences, or trees, or if you like dags).

I've only read a smattering of Pi theory, but if I remember correctly it concerns itself more with the behaviour of `f` and `g`, and more generally bridging between local behavioural descriptions of components like `f` and `g` and the global behaviour of a heterogeneous system that is composed of some arbitrary graph of those sending messages to each other.

I'm getting a bit beyond my depth here, but it feels like Pi theory leans more towards operational semantics for reasoning about asynchronicity and something like category theory / monads / arrows and related concepts lean more towards reasoning about combinatorial algebras of computational models.


The thing about inheritance is it limits you to one relation. Composition is not a single relation but an entire class of relations. The user above mentioned monoids. That is one very common composition that is omnipresent in computation and yet completely glossed over in most programming languages.

But there are other compositions. In particular, for something like process connection, the language of arrows or Cartesian categories is appropriate to model the choices. The actual implementation is another story

In general when you want to model something you first need to decide on the objects and then you need to decide on the relations between those objects. Inheritance is one and there's no need for it to be treated specially. You will find though that very objects actually fit any model of inheritance while many have obvious algebras that are more natural to use


"Gluing together in a way that's not inheritance" is useful enough by itself. Most class hierarchies are wrong, and even when they're right people tend to implement th latest and greatest feature by mucking with the hierarchy in a way which generates wrongness, mostly because it's substantially easier, given a hierarchy, to implement the feature that way. Inheritance as a way of sharing code is dangerous.

The thing composition does differently is to prevent the effects of the software you're depending on from bleeding further downstream and to make it more explicit which features of the code you're using you actually care about.

Inheritance has a place, but IME that place is far from any code I'm going to be shackled to maintaining. It's a sometimes-necessary evil rather than a go-to pattern (or, in some people's books, that would make it a pattern like "go-to").


> Most class hierarchies are wrong

One of the most damaging things is when they teach inheritance like "a Circle is a Shape, a Rectangle is a Shape, a Square is a Rectangle" kind of thing. The problem is the real world is exceedingly rarely truly hierarchical. Too many people see inheritance as a way to model their domain, and this is doomed to failure.

Where it works is when you invent the hierarchy. Like a GUI toolkit or games. It's hierarchical because you made it hierarchical. In my experience the applications where it really works you can count on one hand, whereas the vast majority of code written is business software for which it doesn't really.


I don't think that it really is a useful enough definition. There are lots of ways to glue things together that aren't inheritance that are very different from each other.

I could compose functions together like the Haskell `.`, which does the regular f(g(x)), and I don't think anyone disputes that that is composition, but suppose I have an Erlang-style message passing system between two processes? This is still gluing stuff together in a way that is not inheritance, but it's very different than Haskell's `.`.


But both of those avoid the pitfalls of inheritance. "Othering" is a common phenomenon, and I think it's useful when creating an appropriate definition of composition.


But I don't think it's terribly useful; there are plenty of things that you could do that the people who coined the term would definitely not agree with.

Instead of inheritance, I could just copy and paste lots of different functions for different types. This would be different than inheritance but I don't think it would count as "composition", and it's certainly not something you should "prefer".


That's fair. I'd agree that isn't composition. I'm not sure the thing you describe is worse than inheritance.... It's not composition though.


I have always heard "prefer composition to inheritance" also referred to as "has a" instead of "is a." Meaning:

    class Dog : Animal; // inheritance 
    class Car: 
        Wheels wheels; // composition


Yep. "Composition" has many meanings, but in the context of "inheritance vs. composition" it's just referring to "x has a y".


Article author here. Your idea "gluing together things that don't know necessarily know about each other" is basically what the GoF book means: composition is "this object has a reference to that object and uses its public API". They don't mean "this object ontologically contains an instance of that object" in the sense that a car "has" an engine, which is a narrower definition of composition that people frequently use.

It's that broader version of composition—particularly in its extreme realization, delegation—that underlies a lot of the behavioral patterns in the book. For example, the State and Strategy patterns boil down to "this object relies on another object to fill in the behavior here, and there are ways to choose what that other object is", which is something it's easy to arrange with subclassing and the only point of the pattern is to avoid subclassing.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: