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

I think you and the author have posed a false dichotomy.

I avoid "traditional" OO in my own work for the some of the same reasons the author points out; not least of which that traditional classes are a kitchen sink.

But many of the ideas of OO; notably extensionality (what the author incorrectly calls intensionality), I could never do without. I agree with you, that exposing the innards of my data structures is a crime: not only do I lose control over their construction and use (including defining equality), but I'm restricted from ever modifying the structure.

But nothing in FP prevents hiding structure. You can see it all the time in OCaml: a module signature will declare an opaque, possibly parametric, type, as well as a set of operators over that type. The internal structure of that type is never exposed. All creation and use, and ideally comparisons (though it is unfortunately not enforced in OCaml) must go through the module's API.

(Module signatures, it should be noted, may be shared by multiple implementations, permitting compile-time dispatch.)

Yet while maintaining opacity, I am free to dispense with the excess baggage an OO class entails: run-time dispatch; a single "self" (i.e. the "friend" problem); that abomination known as inheritance; all these things I need no longer worry about, and my code can be cleaner and more efficient.



I suspect some of the problems that many people have with OO tend originate from the C++ and relate languages such as Java. These languages aren't really OO in the Alan Kay sense of the term[1]. They are languages with classes, polymorphic inheritance, and object style binding of methods to structures, but they do not feature "everything is an object with message passing".

By comparison, you really see a lot more of the utility of OO in languages smalltalk or possibly ruby[2] where you can extend everything. I know tend to write my ruby (despite it being a multi-paradigm language) in a manner that you describe: FP style with objects hiding the details.

Of course, all of these languages have their strengths and weaknesses and OO isn't useful for everything. I just think OO has gotten a bit of a bad reputation from some of the languages that chose to label themselves OO even when their implementation was only superficial. This bad reputation may lead to dismissal of the whole idea, producing the false dichotomy you mention.

Incidentally, the lack of strict OO (or any language style) in ruby is what I really like about the language. You can be strict OO if you want, but you can also use classic (C-style) imperative programing when it makes more sense (or FP, or whatever).

[1] http://c2.com/cgi/wiki?AlanKaysDefinitionOfObjectOriented

[2] Regrettably, OCaml is one of those languages that is still in my "looks interesting, I should learn that" queue, so I cannot speak to how it implements OO.


OCaml's OO is IMHO not very interesting, beside the concept of "functional objects", which really ought to exist without the rest of the Java-style OO baggage. (Briefly: methods can easily return a copy of an object with some fields modified; and anonymous, structurally typed objects may be constructed.) Otherwise it is standard Java/C++ fare (albeit more streamlined and with better typing).

On the other hand, OCaml is worth learning for the module+type system alone. Every other language could benefit from its ideas; the only language I've seen that's comparable is Coq (which bases its module system on OCaml's). (And the module and type system really work in tandem: there are advanced mechanisms for type structure hiding that aid forward compatibility.)


The module system in OCaml sounds very nice (and we all know what the "O" for!). But there's still a bias towards a sort of static-ness in FP. For example, the use of abstract data types where a Java programmer may use a class hierarchy. Clients cannot extend an ADT: I can't make my own List in Haskell and pass it off to a function.

Regarding the OO "excess baggage," I would respond that what is "excess" depends on the nature of the system. I can understand dismissing that stuff when your program is self-contained. When the only code at play is your own, when you can statically enumerate every type, function call site, etc, it may be hard to see the value in those features.

My project is a shared library, and so is dynamically linked with code written by other teams, perhaps years ago, or even yet-to-written. The system is thus not my program in isolation, but an intimate collaboration between my component and client components. Runtime dispatch, inheritance, reflection, and even occasional mucking with meta-objects are the tools we use to cooperate. This is a type of extensibility that Haskell doesn't even try to support. I don't know about OCaml here.

(Alan Kay called this the "negotiation and strategy from the object’s point of view.")


In the same way that many recommend programming to interfaces rather than concrete classes in static OO languages, programming to typeclasses rather than concrete types (when you can't avoid that kind of dependency and write completely generic code) is an important recommendation in Haskell.


> The module system in OCaml sounds very nice (and we all know what the "O" for!). But there's still a bias towards a sort of static-ness in FP. For example, the use of abstract data types where a Java programmer may use a class hierarchy. Clients cannot extend an ADT: I can't make my own List in Haskell and pass it off to a function.

Depends on what your function accepts. If it takes explicitly a list, you're screwed, but it clearly was never intended to be generic. If it accepts something Foldable or Traversable, just make sure your data structure has an instance for these type classes.

In OCaml, you can have objects and inheritance if you absolutely want to, but you can get a lot out of structural typing before going there. If you want extensible ADTs, you can, but you need to plan for it by using polymorphic variants [1] at the expense of some safety.

1: https://realworldocaml.org/v1/en/html/variants.html - scroll down to the "Polymorphic variants" section


> My project is a shared library, and so is dynamically linked with code written by other teams, perhaps years ago, or even yet-to-written.

Constructing a component architecture is the goal of many approaches, and shared libraries is one expression of that ideal. When a library is compatible with the calling application and the OS, a library can closely approximate an ideal component.

However, components, applications and OSs are not static but constantly changing. In order for a library to be a component used by many other entities, the library must be continually (at least frequently) curated to remain compatible with all the other components it cooperates with.

While the point of a library is to abstract an API so users of the library don't have to think about how it's implemented, the creator of the library must consider those details very deeply.

Whatever techniques or languages are used to create a library, OO, FP, both or neither, the most important consideration is that its source code is clear, concise, logical, and understandable. The library I create today will decay if not maintained, and if I'm not around, how easily can someone pick up where I left off?

The inevitable tricks employed making procedures or methods work in real code will not be obvious to our successors. Good ideas, even embodied in obsolete code can be useful if clearly expressed and adequately explained. Thorough documentation transforms the work into lasting value.


> (Module signatures, it should be noted, may be shared by multiple implementations, permitting compile-time dispatch.)

With first class modules, you can even get runtime dispatch, just build a new module dynamically, selecting the concrete implementation depending on, say, on a command line parameter.




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

Search: