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.
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.
Ah, well there's the confusion. A language with semantically impure closures emphatically does not reproduce the semantics of the lambda calculus unless you restrict yourself to only pure, immutable variables. But if you reproduce the lambda calculus, there's no need for closures as we know them. It's just an implementation that happens to work well. You could supercompile the lambda-abstraction at the application site and avoid having a closure. It's just not a good idea.
But I agree, in a language with mutable variable capture, you can reproduce the semantics of OOP by providing a bundle of functions closed over mutable references to the "object" data.
> a language that bills itself as functional that doesn't provide that capability natively
I'm not sure exactly what capability you're referring to. Could you clarify a little bit?
(* foo.ml *)
let bar =
let foo = lambda x . lambda y .
if y == 0 then { getFoo : lambda z . 0 }
else { getFoo : lambda z . (x * z - x + z) / y }
end
in foo 3
(* bar.ml *)
include foo.ml
let y = bar 5
bar.getFoo(4)
It sure seems to me that bar is hiding the 3 from bar.ml (and there are much more complicated examples where foo.ml really can't get at the captured type, for instance), and at the same time it's capturing the value of 5. The function getFoo on bar is bound to it (the record type can desugar to more curried lambdas, if necessary, it's just tedious and this illustrates my point better).None of this seems to have anything to do with mutation, to me, though it certainly seems to have both information hiding and binding logic and data. The fact that it can be compiled not to use closures (using whole-program compilation) is immaterial; the same is true for the same program specified with objects in mutable languages (SML has both mutation and closures, and MLton does exactly that).
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.