Hacker News new | ask | show | jobs
by naasking 3453 days ago
Classes tightly couple functions on types with the type declaration itself. It's often not even clear in which class certain methods belong.

Better to separate data types from functions on data types the way it's done in functional languages, and if you still want object-oriented programming, you can support it like Ada does, or as syntactic sugar over the ordinary call syntax like some functional languages do.

2 comments

With a few exceptions, functions and operators are allowed, in C++, to exist outside a class, which addresses your (valid) concern.
Better why?
Superior composability with simpler semantics and sounder type systems.

Class hierarchies are inherently unfriendly to strong, sound type systems. Interface composition isn't.

Composing interfaces is clean and straightforward. Composing classes isn't, and you run into things like the diamond problem.

Classes do not imply inheritance. Classes are a means of defining an interface, albeit a heavy one.

The statement that it is better to separate data and behavior is far from self-evident IMO, and I would be very interested to understand why this is such a deeply held belief. Seriously, why?

> The statement that it is better to separate data and behavior is far from self-evident IMO, and I would be very interested to understand why this is such a deeply held belief. Seriously, why?

Data is and always has been more valuable and more important than behaviour. It's not unheard of to preserve accounting data that's decades old, even though the accounting system itself changed completely many times. The converse, preserving old programs to run on brand new data is much more rare.

The whole hoopla around big data should make it clear where the real value is. Behaviour is merely a way of transforming data, and coupling the two rarely works well in the long run. The only times it's actually advantageous is to preserve invariants that ensure input or output data is well formed. For instance, data structures that ensure or preserve orderings, etc.

It's not always true, by any stretch of the imagination. Closures couple data and behavior and most (though not all) functional programmers consider closures to be pretty central to FP. I'd add that closures are hard to use in that way without allocating, which is why I think one potentially reasonable argument against coupling data and behavior is performance-driven, but that's a more subtle argument that ignores compiler performance (and there are times where you lose more from all the extra code taking up your icache than you gain in speed due to not allocating; plus, as C++ and Rust demonstrate, you can have closures without allocation if you're willing to heavily restrict them).
> Closures couple data and behavior

The use of closures in functional programming is an implementation detail, not a fundamental aspect of the underlying theory, and closures only "couple" data and behavior in the very literal sense that it is a function that happens to point to some data under the hood, not in the syntactic or semantic sense we're referring to when we say that OOP classes couple behavior and data.

A closure in a pure language can't couple to data any more than a lambda expression without free variables (from the programmer's perspective), because they can have the exact same type and semantics.

In other words, there's no semantic difference between "f = \x . x + 2" and "f = let y = 2 in \x . x + y" even though the latter is a closure in the absence of inlining.

Purity seems to be totally orthogonal to this question, to me. I'm referring to the ability to return thunks from within called functions, such that the data are totally encapsulated in the closure (are not passed in explicitly and cannot be retrieved manually). That seems to be coupling data and behavior to me, since you can't run the function without the data it was coupled with at construction time. Whether that's fundamental or not is, of course, a subjective question, but I have a hard time thinking of a language that bills itself as functional that doesn't provide that capability natively (Rust and C++ make it difficult, but neither really bill themselves as functional).
> Seriously, why?

Personally speaking, it's just a lot easier. I could go on about various theoretical justifications, but at the end of the day it's just easier. This probably has something to do with the fact that you don't really gain anything by defining data and behavior together, but you do lose flexibility by coupling those possibly orthogonal concerns.