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

Much of OOP and FP is very similar. In fact, closures and objects are basically equivalent: each can be implemented with the other. So then the differences between OOP and FP are largely a matter of style, and arguments consist mostly of anecdotes.

But OOP has some concrete advantage over FP, and vice-versa. Specifically, OOP makes it easier to add variants. If, at some point during the course of programming, you decide you need a new variant, it's simple. You make a new subclass. On the other hand, FP makes it easier to add operations. You just write a function. So you wind up with a matrix:

               | OOP    | FP
    ---------------------------
    Variants   | Easier | Harder
    Operations | Harder | Easier
This has been independently observed by several programming language researchers. Then the question becomes, how can we make both easier at the same time? This is the Expression Problem. The Expression Problem is solvable in several existing programming languages, but the solution is often ugly and convoluted. So I like to add an additional restriction: how can we make both easier without lots of ugly, confusing boilerplate? Things like multimethods and typeclasses get us part-way there, I think.

While I love FP, it's important to not write-off OOP. There are some problems which are more naturally solved by OOP. Good ideas are good, regardless of associated paradigm. Then, OOP should not be an object of disdain, but an object of inspiration: how can we take all of the good parts and remove all of the bad parts? How can we advance the art and power of programming?

If we want to see improvements, we have to keep an open mind.



After a recent discussion on this topic on the Haskell Reddit I posted that I've begun to the see the Expression Problem in terms of data and codata [1].

In very brief, data tends to be completely transparent in its construction, finite (or tending toward finiteness), possessing obvious structural equality, and can be easily transformed in myriad ways via induction.

Codata on the other hand comes from the other side. It is completely transparent in its destruction (observation, think object properties), tends to be infinite (think how the process of a state machine is infinite), possesses obvious entity identity, and can be easily created in myriad ways via coinduction.

In most programming settings, these are just perspectives as they can easily coincide, but you can observe the difference when you program in a strongly normalizing like Agda.

I feel that OOP has built most of its abstraction on the assumption that codata-oriented (object-oriented, kind of) modeling works well. For this reason, it's easy to talk about black-boxed domain models which are only predictably observed and classified, but it gets hairy to talk about equality of two trees---you're forced to think about pointer equality even when structural equality might be a more natural fit.

FP doesn't necessarily pick one side or the other, but it certainly makes data more accessible (algebraic data types being as terse as they can be) leading to the Alan Perlis "100 functions" quote.

Greatly powerful things can come about when the two sides are combined or emphasized for their strengths. In particular, the `pipes` ecosystem being built in Haskell right now straddles the line by being perhaps best modeled as a hybrid data/codata type. This means that you can both somewhat easily construct pipes inductively while also observing the infinite process (codata) they represent.

I don't think anything I'm saying is particularly new to researchers, but I personally have been finding it incredibly instrumental when talking about how OOP and FP differ.

[1] http://www.reddit.com/r/haskell/comments/1quhrl/from_object_...


Interesting!


Take the pedagogical variants/operations example: a word processor, where you have different kinds of items on the page (pictures, paragraphs, etc) and different operations (drawing, layout, etc).

In an FP language, if you want to add a new operation, say spell check, it's easy. Just write some functions operating on the variants. In OO, you have to break your algorithm down into methods and add a new method to each class.

Now, say you want to add a new variant, say embedded video. In an OO language this is easy, just add a new subclass and implement methods for layout, drawing, spell-checking, etc. However, in an FP language, it's still easy. Add the new variant, compile, then fix the places where the compiler complains about non-exhaustive pattern matches. I posit that fixing these errors takes no more work than it does to write the methods for the OO case.

The big benefit of the FP organization is that if you want to understand or rework the layout algorithm, all the logic is in one place. I think losing this benefit totally outweighs any benefit gained in making it marginally easier to add a variant.

Note that OO programs often use the visitor pattern, which is just a very awkward way of expressing a switch statement that checks for exhaustion.


It's not that adding new variants in FP is completely hard, just that it's harder than in OOP. When you add a subclass, all its operations are confined to one section of code, one module. In FP, you'd have to modify perhaps several different sections of code, possibly in separate modules. Likewise, it's not prohibitively difficult to add operations in OOP, it's just easier in FP.


In the word processor example, OOP makes it easy to see how say the image object participates in layout, rendering, printing, etc. However, it makes it much harder to see how the layout algorithm or the rendering algorithm or the printing algorithm works. FP has the opposite set of strengths and weaknesses. Easy to see how an operation works, hard to see all the ways a variant is used.

But its not realistic to treat both cases as being of equal weight. In practice, your work will be doing something like fixing the layout algorithm or optimizing the rendering algorithm. Being able to easily grok an operation is much more valuable than being able to easily grok the behaviors of a particular variant.


I agree and disagree. FP's strength is you can layout your code in whichever way makes most sense for your project: by what they operate on or by what they do.

Grouping by operation makes sense until a certain point, after which you have to subdivide. If I had an FP based word processor example with 3 operations but 60 items I'd probably group by items, not operations.

Imagine if FP forced every append-to-collection-like operation into a single source file. It would be madness.


I see what you're saying, and I agree. Just to be sure:

The most complicated parts of programs are typically the operations. They're also the most likely to need additions, changes, or optimizations. Thinking about it now, I think most programs are far less likely to need new variants than operations, which is something I hadn't considered.

I still contend there's value in trying to make them both easy (for those relatively rare times when they're needed).


I wouldn't say closures are what make functional programming functional. I tend to think like that too, but it's probably a bias from my Lisp/JavaScript background. Closures are orthogonal both to OOP and FP.

IMHO functional programming is just:

  - Higher order functions
  - Referential transparency
  - Immutability
Closures are just a convenient way of encapsulating state avoiding global variables.


Similarly, I wouldn't say objects are what make object-oriented programming object-oriented. It's a different approach to organizing and structuring a program. Objects are just one part of that approach, just like closures are just one part of the functional approach. The fundamentals of the two approaches are more similar than people often make them out to be, even if the approaches appear rather different.


What are the fundamentals of OO? Those principles of immutability and referential transparency seem to require writing pretty unidiomatic code when working with OO languages.


Rather than saying the fundamentals are more similar than given credit for, perhaps I should say the fundamentals overlap more than given credit for.

I'm having a hard time finding a concrete example at the moment, but I've seen fragments in Java codebases that, if you squint a little, seem almost functional. Of course, the code was very, very ugly, but semantically was quite functional.

I'm not trying to defend OOP. My view is that the benefits of FP outweigh the benefits of OOP. If we could find a way to import the benefits of each into the other, we'd have the best of both worlds. (Of course, then everyone would find something new to argue about ;) ).


It isn't harder to add operations in languages that don't confuse class and object, e.g. if you have a dynamically typed language you could just add whatever operation you needed for a specific object without worrying about hierarchies. The question is whether that just replaces one problem (brittle hierarchies) with another (method-not-supported errors).


I failed to mention that the expression problem, as originally formulated by Wadler[1], is in the context of typed languages. One of the arguments in favor of untyped languages is that they are indeed more flexible that way, but at the expense of type safety. As the science of type systems advances, typed languages are slowly becoming more and more flexible (and, sadly, difficult to learn).

[1]: http://homepages.inf.ed.ac.uk/wadler/papers/expression/expre...


I should have read your original post more closely, hadn't even heard about the Expression Problem. Makes more sense now after a bit of reading - I guess Haskell's notions of static typing are quite different from that of the C family of languages.




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

Search: