See: http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom....
See: http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom....
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.
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.
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.
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).
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.
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_...
IMHO functional programming is just:
- Higher order functions
- Referential transparency
- Immutability
Closures are just a convenient way of encapsulating state avoiding global variables.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 ;) ).
[1]: http://homepages.inf.ed.ac.uk/wadler/papers/expression/expre...
If the problem domain you're trying to grapple with is dominated by algorithms, an idiom that prefers verbs to nouns may be helpful.
If you're working in a very complex business domain, where the algorithms aren't especially interesting but the relationships between entities are, nouns are helpful.
Clojure and Haskell programs often utilize named types. Several OO languages have recognize the need for first(ish)-class function management. Ruby, Python, and Javascript are all good examples of languages that have attempted some compromise between the two. They are justifiably popular.
Good OO code isn't as different from good FP code as most partisans imagine. Remember that all good code has to satisfy at least three audiences: the computer, fellow programmers, and third-party consumers (who may or may not be either of the above). They all have different needs. Don't discount the importance of nouns for that third category.
Because someone heard pg say he's not overly fond of overly OO code (which he has said in the arc design document), or some other well-known thought leader on programming (like Yegge), they then form a particular dislike of all OO code and even the idea of OO, itself. It's not rational or very fully formed for most of the folks who hate OO. Likewise, there are folks coming from (maybe?) a Java or C++ background, who feel that strict OO is the only sane way to approach designing a program.
I'm not going to try to give examples of good or bad code in either category...but, will point out that Haskell is not an OO language (in the Grady Booch sense of OO, though you can encapsulate methods in data because it has first class functions), but there's an awful lot of beautiful and correct Haskell code out there. Likewise many other functional, Lisp, and ML-derived languages. OO design can obscure meaning, but there's no reason to get reactionary about it. It's one useful tool; it certainly works great for UIs and some types of API.
But we can do better with existential types
data Counter =
forall x . Counter { _step :: x -> x
, _view :: x -> Int
, _state :: x
}
step :: Counter -> Counter
step c@(Counter {..}) = c { state = _step _state }
view :: Counter -> Int
view (Counter {..}) = _view _state
At this point, we're passing around the internal state as "some type x" which can never be inspected, achieving encapsulation. We can create general functions that manipulate Counters stepTwiceThenShowEm :: Counter -> Int
stepTwicethenShowEm = view . step . step
and we can easily create alternative implementations intCounter, integerCounter, listCounter, unCounter :: Counter
intCounter = Counter succ id (0 :: Int)
integerCounter = Counter succ id (0 :: Integer)
listCounter = Counter (():) length []
unCounter = Counter id (const 4) ()
We also now must start thinking of our "objects" as entities because we cannot compare their internal state for equality (unless we give it an Eq constraint). This begins to quickly demand we keep some notion of state as we need to model "pointer equality" pretty quickly.We can even do HAS A subtyping. Lenses make this very powerful.
data TwoCounters =
forall x . TwoCounters { _c1 :: Lens' x Counter
, _c2 :: Lens' x Counter
, _state :: x
}
There's no doubt that this is an uncommon technique in Haskell, but it's worth stating that there are a LOT of similarities between codata modeling like this and typeclasses.Finally, I began to follow a lot of this due to a middling popular library `folds` [1] which describes very generic ways of traversing data structures and accumulating some kind of result state. Basically all of the types dealt with in this library (L1, L1', M1, R1, L, L', M, R) are existentially quantified codata "objects" like I described above. This makes for very general code.
Here's a link to get started on studying xmonad's code: http://www.haskell.org/haskellwiki/Xmonad/Guided_tour_of_the...
Incidentally, it's like only 1000 lines of code, so it's super efficient and super optimized.
It's very different than typical Java code, where an object will "know" how to do non trivial things to itself, and the pieces of an algorithm end up spread over a dozen classes in a dozen different files. Such code will often use elaborate class hierarchies breaking down pieces of an algorithm into objects that interact with virtual method calls, all because of a crippling fear of switch statements. This cuts off the nose to spite the face, rendering the code incomprehensible as a result.