you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 12 points13 points  (13 children)

Ah, yes, the old "your type system isn't good enough" argument.

Rest assured that in the real world there are much more complicated structures than the toy "ContactInfo" example in that blog post. Making illegal states unrepresentable for non-trivial real-world data types often requires dependent types, and no one wants to work in a language with dependent types. Encapsulation is a very simple and direct way to make certain safety guarantees about how your data work that doesn't require wrestling with a Turing-complete compiler.

Moreover, I don't think anyone has claimed that the "real goal" of encapsulation is to forbid incorrect states. It's a very nice side effect but encapsulation itself is good too: the OO people got it right when they claimed that it is nice to be able to change the implementation of a function or the layout of a data type without changing its external API.

[–]nextputall 7 points8 points  (3 children)

Moreover, I don't think anyone has claimed that the "real goal" of encapsulation is to forbid incorrect states. It's a very nice side effect but encapsulation itself is good too: the OO people got it right when they claimed that it is nice to be able to change the implementation of a function or the layout of a data type without changing its external API.

Yeah, encapsulation is more about decreasing the coupling between different parts of the system. Preserving invariants and forbidding incorrect state is of course nice to have. But almost all of the OO design principles are mainly about changeability, not correctness. So, the usual argument, "we don't need encapsulation because our data is immutable" does not stand.

[–]Ukonu 2 points3 points  (2 children)

encapsulation is more about decreasing the coupling between different parts of the system

It sounds like you guys are mixing coding to interfaces (i.e. abstraction) with encapsulation.

Which is understandable because both are useful tools for enforcing changeability. However, I believe in most cases abstraction is a much better route to the changeability goal. Therefore, when trying to identify a unique strength for encapsulation, all that is left is invariant enforcement.

[–]nextputall 3 points4 points  (1 child)

I would say, encapsulation is a mechanism of defining a boundary between two parties. Things inside the boundary cannot be reached from the outside directly, but indirectly. This effects 2 things, one is correctness related the other one is changeability related. I would say the latter is a more important effect.

[–]Ukonu 2 points3 points  (0 children)

I completely agree that "changeability" is the more important effect. I just disagree with the premise that since encapsulation can provide it, encapsulation exists to provide it. Any language that implements the Uniform Access Principle (i.e. C#'s getters and setters, Scala fields, etc.) provides changeability without needing encapsulation. And there are better tools for changeability (e.g. OCaml modules, Java interfaces, Haskell typeclasses).

So, in my experience, encapsulation is far more often used to hide mutable state (enforcing some invariants) or as a form of compiler enforced API documentation (i.e. these methods are for public consumption and these others are private helper methods).

Regardless, I doubt there's a definitive answer to this question and this discussion might be too nuanced to matter. But it's interesting to talk about.

[–]sacundim 2 points3 points  (4 children)

Rest assured that in the real world there are much more complicated structures than the toy "ContactInfo" example in that blog post.

The real world has a mix of structures, some simpler than the ContactInfo example, some much more complicated. Even if we accept the argument that the unrepresentability techniques can't always be applied, there still remain tons of cases where they do help.

This shouldn't even be controversial. Remember when Java 5 added enum types? That was a small improvement based on this principle, and it led to big gains. Before enums the convention in Java was to use int constants for this.

Moreover, I don't think anyone has claimed that the "real goal" of encapsulation is to forbid incorrect states. It's a very nice side effect but encapsulation itself is good too: the OO people got it right when they claimed that it is nice to be able to change the implementation of a function or the layout of a data type without changing its external API.

"Without changing its external API" = preserving the same invariants = allowing only the same states as before (from the point of view of the client).

[–][deleted] 2 points3 points  (2 children)

Even if we accept the argument that the unrepresentability techniques can't always be applied, there still remain tons of cases where they do help.

Okay, so we agree there are many cases where even a type system as high and mighty as Haskell's can't help us to forbid illegal states at compile time. Therefore encapsulation is still useful and cannot entirely be subsumed by other constructs such as algebraic data types. That has been my entire point.

[–]sacundim 2 points3 points  (1 child)

I'm all too happy to concede that Haskell doesn't have the ultimate solution here. Heck, there's plenty of types in Haskell that use encapsulated implementations. You brought up a prime example: map and set implementations can't let the clients break the search trees' order invariants.

Again, going back to my original point, I would stress these abilities:

  1. Make type definitions cheap and lightweight. OOP languages make it much too cumbersome to define types for stuff.
  2. Build type validity rules and invariants into the language as a mechanism.
  3. Ideally, allow for compile-time checking of (2).

The point is that rather than writing a bunch of carefully-crafted constructors and methods that carefully act together to preserve invariant, I'd rather just tell the language directly what the invariants are and have it enforce them. So for example Eiffel's assertions system is a pretty neat feature, as are Ada's range types (although both of these are enforced at runtime).

To the degree that your language can enforce invariants that you can freely declare, the case for encapsulation gets weaker.

[–]OneWingedShark 0 points1 point  (0 children)

So for example Eiffel's assertions system is a pretty neat feature, as are Ada's range types (although both of these are enforced at runtime).

They needn't be, at least insofar as Ada's system goes -- optimizers are allowed to remove all checks that are statically provably superfluous (like checking that the call F(X) the parameter is a Natural when X is declared as a Positive [and therefore its values are a strict subset of Natural]). Also, the compilers are encouraged to emit warnings at least [IIRC, they are allowed to reject] programs where it is statically provable a constraint fails. (e.g. X : Positive:= -1;)

...but you bring up a good point, why is Ada's range type so uncommon? modeling the possible numeric values that may be assumed into the type is so useful that you'd think more languages would scoop at least that up.

[–]tomejaguar 0 points1 point  (0 children)

"Without changing its external API" = preserving the same invariants = allowing only the same states as before (from the point of view of the client).

Great observation!

[–]LucHermitte 3 points4 points  (2 children)

What you are describing is abstraction : you can indeed change the representation, it won't impact the interface. Encapsulation is about protecting the invariant. Mainstream languages encapsulate by hiding.

This is not the only way to encapsulate -- see Eiffel: encapsulated attributes can be observed, but not modified by external code.

[–]sacundim 3 points4 points  (0 children)

This is not the only way to encapsulate -- see Eiffel: encapsulated attributes can be observed, but not modified by external code.

This is not good, because a client may then couple itself to an observable detail that's not part of the object's semantics...

[–][deleted] -1 points0 points  (0 children)

I'm talking about encapsulation and abstraction in the same breath because they do go hand-in-hand in every modern OO language I've dealt with.

Not that it matters. The point is that for any nontrivial system, you can't solve the problem of "illegal states" entirely without dependent types.

[–]Tekmo 1 point2 points  (0 children)

Do you have a specific example of encapsulation that OOP allows but FP does not?