The big advantage of OO is that it acts as a distillation of how humans think. We're accustomed to thinking in terms of 'things that do stuff'. What OO provides is essentially a skeuomorphic element to your code, where your basic units have some resemblance to real items and concepts. This makes it a lot easier to reason about large codebases, and makes them easier to document (in theory). It means the most important thing you need to understand how a piece of business logic works is knowledge of the business. You don't need to know anything about the data, or how the application is composed in order to comply with the needed use case. If you know the concepts and their behaviour in the problem domain, you will be able to make sense of how the code is written.
FP has its place, as does procedural code, but advocating either of them as a complete replacement for OO is short-sighted. Claiming FP is a solution to OO's shortcomings is too. Each has its place. If anything, we should be working towards next-generation models, that combine the advantages of both and mitigate the downsides of both.
Honestly, while I think OO programming in the broadest sense does that, I think class-oriented OOP (what the article mostly focusses on) languages, particularly statically-typed class-oriented languages in the C++/Java lineage--don't do a great job of either supporting that intuition or facilitating applying it intuition to the construction of correct, maintainable computer programs. They aren't useless in that respect--there is a reason why they flourished--but there's also a reason that after them an important programming paradigm for several decades, and a dominant one since at least the late 1980s, there's a whole lot of moves away from traditional class-oriented OOP in newer languages.
I think that we're seeing a model--which is mostly viewed as being within the FP tradition but which has learned a lot from OOP languages and supports a lot of OO thinking--emerge (and that this article points to a lot of its elements) that provides a framework that better matches the intuition than class-oriented OOP does, while also better supporting building correct, maintainable, easy-to-reason-about systems.
My first thought is that this is dependent on implementation. It is possible to write classes in a way that aligns with intuition, but it is sometimes hard to do that, and even if you're great at OOP it is hard to do consistently. I think the Smalltalk message-sending way of thinking has huge value because it is easy to reason with, and facilitates this intuition.
That said, I do see tremendous value in FP, and I'm encouraged by the elements of FP that I've seen popping up in Swift. So I guess ultimately I do agree with you. I'd like to see OOP continue to flourish, but borrow elements from the Functional style that make it very difficult to write fragile code.
Citation needed.
Browsing around Google Scholar for variations of "object oriented empirical comparison" shows a huge body of research comparing various OO approaches to each other, but very few comparing OO approaches to anything else. Those which I have been able to find compare OO to procedural code, and find either no significant difference in comprehension levels, or that procedural code is easier to understand (ie. closer to "how humans think") than OO:
An empirical study of novice program comprehension in the imperative and object-oriented styles
http://ftp.cs.duke.edu/courses/fall00/cps189s/readings/p124-...
Assessing the cognitive consequences of the object-oriented approach: A survey of empirical research on object-oriented design by individuals and teams
http://arxiv.org/pdf/cs.HC/0611154
An exploratory study of program comprehension strategies of procedural and object-oriented programmers
http://www.ptidej.net/courses/inf6306/fall11/slides/11%20-%2...
I've only been able to find one source comparing OO with functional programming, which didn't measure comprehension. Instead, it compared code quality metrics between C++ and Standard ML. Most showed no significant difference, except for SML taking longer to run its tests (it also had more tests), having higher code and library re-use and having a larger number of errors per 1000 lines (although the same number of known errors overall):
Comparing programming paradigms: an evaluation of functional and object-oriented programs
Aside from official sources, it's a statement that I didn't think needed much citation. It's absolutely fair to criticize it, but it's a widely-held belief that seems to hold true (at least anecdotally)
I would consider this a disadvantage, not an advantage. Human thoughts are imprecise and carry rich but ambiguous connotations. The major advances of mathematics in the 19th century (or thereabouts) onward are closely tied to the divorce of notation and natural language.
The ancient version of the Pythagorean theorem is something like, "The area of a square whose side is the length of a hypoteneuse of a right triangle is equal to the sum of the areas of squares whose sides are equal in length to the other two sides." A modern version is more like, "Let a, b, c be the lengths of sides of a right triangle, where c is the length of the hypoteneuse. Then a^2 + b^2 = c^2." You can see as time goes on, the text becomes simultaneously clearer, more concise, and more abstract.
This is why natural language programming is doomed to failure. Others have expressed the point better than I do (notably Dijkstra). But you can look at the examples of how inheritance in class-based OOP systems work and you will get sick just looking at them: examples of Dog inheriting from Animal, and Penguin from Bird (but penguins don't fly), et cetera. The real disadvantage here is that once people start thinking about taxonomy in general (which class-based OOP encourages), you're not thinking about programming any more.
The difficulty people have with even simple problems like "how do you describe squares and rectangles in a computer program" is another good illustration of how the class-based object-oriented system of thinking is unsuited for solving computational tasks, at least compared to the alternatives.
It seems to me that you're sort of confusing OOP with programming interfaces.
An interface describes a model of how to use a certain piece of code, and indeed a good one can be almost "obvious" and it begins to feel like a "natural" way to reason with it. But it's all about the interface: not about from what kind of language constructs the interface is composed of.
Interfaces can be done in object oriented programming too but for some reason, possibly exactly because of the resemblance to real items and concepts and how human people are so fond of those, most interfaces in OOP languages are utter horror.
This could be because it's so easy and almost fundamental to create "things that do stuff", i.e. classes in OOP, there will be a lot of them. The design pattern craze kind of formalized that for good, which is why we've all enjoyed our share of abstract factory builder singleton visitor, or whatever.
In contrast, you tend to see really good interfaces surprisingly often in... C.
As a disclaimer, this is a big reason why I like C language -- it forces you to think simple because it offers so many guns to shoot yourself in the foot and a lot of other places too that you need to focus on what's essential to your program. With an easier but inevitably more complex language, the lull times in the day of programming seem to produce lots of implementation and interfacing complexity that a C programmer would never dare to attempt. C programmers also use paradigms associated with OO but only where appropriate because it's a bit of hurdle to implement those in C.
I've also observed that there's a vague parallel between complex (object oriented) interfaces vs. simple (C) interfaces and the Alan Perlis' notion of to better have 100 functions operating on one data structure rather than 10 functions operating on 10 data structures. Good OO interfaces tend to be really simple and short, even so that they're not even particularly OO anymore.
The OO, procedural, FP paradigms are mostly orthogonal and can be combined freely in sensible amounts where needed, but they're also orthogonal to concepts such as encapsulation, polymorphism, type hierarchies, and inheritance which themselves aren't tied together either.
Steve Yegge's "Execution in the Kingdom of Nouns" has some interesting commentary on the idea of OO being in any way how humans think. The allegory has some harsh things to say about Java, which are not my intention in suggesting this link. The observations that objects tend to be nouns while functions tend to be verbs is very interesting.
/* footnote 3 is hilarious */
I first encountered functional programming in the 1980s on my Computer Science degree course a few years before I encountered OOP (CLOS and then C++) and I'm not convinced that OOP is fundamentally closer to "how humans think" than FP. Most programmers these days were taught an OOP language first so think that is most natural - but I don't think there is anything fundamental about that.
Objects are always a struggle. I see students new to programming take as long as their junior year before they even start to "really get it" and design reasonable classes.
Rolling identity, state, values, types, functions, polymorphism, modules, resource management, and who knows what else into a single thing is inherently going to result in something pretty damn complicated, compared to trying to keep those concerns orthogonal and thus only pulling in those that are needed for the task at hand.
I would certainly say that OOP-based software engineering has vast advantages in certain cases, but my hypothesis would be that its "ease" advantages is more one of mindshare rather then something inherent in the human mind. This hypothesis, however, is worth extensively testing... for a discipline that relies on stretching human cognitive performance to its limits, we spend far too little time actually figuring out exactly what limits those are.
No, skeuomorphism involves ornamental details, which are at best hints about how something is to be used.
How you break-down the responsibilities of your system and name them is much much more important than that.
Let's not confuse "how humans think" with "how humans ought to think to self problems effectively". There is little evidence that OO is any better than other principled approaches to programming.
With how widespread OO is now and in the last decades, how much it is taught and how important it is in a fair share of popular programming languages (or even mandatory, for all practical purposes), this point might just be a self-fulfilling prophecy (if I'm using that expression correctly).
Alan Kay wrote "The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be."
To start to see what this means, consider the annoying String / Data.Text split in Haskell. String is very much in the "leave data alone" mindset, baring its guts as a [Char]. Now you're stuck: you can't change its representation, you can't easily introduce Unicode, etc. This proved to be so rigid that an entirely new string type had to be introduced, and we're still dealing with the fallout.
Great and growable systems! The large scale structure of our software, decomposed into modules, not just at a moment frozen in time, but in the future as well. We are tasked with thinking about relationships and communication.
So here is how "the better way" is introduced:
> Data is immutable, and any complex data structure...
There's that insidious FP bias: to immediately dive into data structures, to view all programs as a self-contained data transformation. So reductionist! It's completely focused on those "internal properties and behaviors" that we were warned about above.
I would end here, but I just couldn't pass this up:
> To come up with a better solution [for dispatching], Haskell and Clojure take very different approaches, but both excel what any OO programmer is commonly used to.
"Any OO programmer?" No way! OO as realized in truly dynamic languages exposes not just a fixed dispatch mechanism, but the machinery of dispatch itself, i.e. a metaobject protocol:
"...there are not only classes for workaday tools, such as UI widgets, but there are also classes that represent the metastructure of the system itself. How are instances themselves made? What is a variable really? Can we easily install a very different notion of inheritance? Can we decide that prototypes are going to work better for us than classes, and move to a prototype-based design?"
This is far richer than the anemic switch-style dispatch that Haskell's guards and pattern matching provide. For example, try modifying a Haskell program to print a log every time a string is made. You can't!
I'm not familiar with Clojure but I'll bet its object model has its roots in CLOS. Whether or not you call it "object oriented," CLOS is solidly in the spirit of having a dynamic meta-structure.
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.
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.
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.")
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.
There are a lot of things wrong with, say, Haskell '98 from the perspective of a modern Haskell programmer. Strings are one, but monads aren't applicative functors, it took us a long time to figure out how we wanted to write monad transformers, lazy I/O is terrible and we should use conduits or whatever instead. But you picked strings. This example does not help your point for the following reasons:
1. You can't just change the implementation of the string type without messing up someone's program. For a fantastic example, look at the recent change to Oracle's string type in Java. In theory, the interface is the same. In practice, it made a bunch of people mad.
2. You can encapsulate data in Haskell. Look at the "Text" data type, and ignore Text.Unsafe which exposes the gory innards. This is module level encapsulation, which is just as good as class-level encapsulation (better, actually, since it's more flexible). You could replace Text with a UTF-8 implementation or a UTF-32 implementation or some magic implementation that switches between the types, and you wouldn't break consumers of the Text interface.
> For example, try modifying a Haskell program to print a log every time a string is made. You can't!
This is a really contrived example. First of all, there is the question of whether you will need to create a string whenever you log something to a file, and presumably you wouldn't want to log those strings. Second, this is something you'd do with a debugger, you wouldn't actually do this to a program.
Besides, if you had access to the string implementation (which I'm assuming here is Text, because that's what most people use), you could just put some kind of unsafePerformIO call in front of uses of the Text constructor, and since the Text constructor isn't exported from the Text module, you're done.
Yeah, you can. 'NSString' is in fact a class cluster that provides different implementations/representations. Well, used to be on OS X, because it was changed to be a wrapper for a single CoreFoundation representation.
In GNUstep and Cocotron, I think they still use the older class-cluster implementation, and programs are portable between these implementations.
Polymorphism, baby :-)
You're conflating the fact that Haskell had poor modularity when it was first conceived and String first defined, with the claim that only OO can provide the necessary modularity.
Clearly ML modules provide and always provided the necessary modularity to abstract over string representations, but there's no OO in most MLs. And now with support for ML modules as first class values, we don't need objects for modularity at any level of programming.
The core FP idea is to focus on immutable data and data transformations. This is the minimal set of concepts one needs to juggle to get computations going. When modules communicate, they need to pass data and identify the transformations, so there is no dichotomy here between FP and OO (!). Especially if you think of method tables as data.
The String / Data.Text split in Haskell is an artefact of Haskell's ecosystem. It is not a conceptual hurdle, but rather an implementation detail. It is not too hard to imagine a different FP ecosystem where one can readily substitute different implementations under the same immutable data structure API, all with very explicit parametrization of the data transformations. All of (1)immutability, (2)simple data API, (3)polymorphism and (4)explicitism are important. Note that OO systems encourage (3), while FP systems encourage (1), (2) and (4).
Code as if you have immutable data and apply data transformations, tune performance by using the best implementations under the common simple data API. The question becomes how to build a system where all of them are ergonomic to use. IMHO, Haskell is not quite it, rather places like Dart / C# offer better ergonomics.
The other example is also thought provoking. In a system with polymorphism support, it's relatively straightforward to supply one's favorite String implementation, including one that prints a log on every String construction. The question is how to provide the new module to clients, which is reminiscent of dependency injection, but concrete implementations of DI are magic bad. In an explicit style, this would be realized by making modules functors of other modules and explicitly passing in the method tables:
function Foobar(string)
return {
foo: function(x)
return string.concat(x, string.new('abc'))
end
}
end
function main1()
string = String()
foobar = Foobar(string)
foobar.foo(string.new('xyz'))
end
function LoggingString()
return String() + {
new: function(x)
print(x)
return String.new(x)
end
}
end
function main2()
string = LoggingString()
foobar = Foobar(string)
foobar.foo(string.new('xyz'))
end
But it takes discipline to write the above and not sprinkle the code with String().new(...) everywhere, which defeats the purpose.Type classes are the definition of polymorphism. And you can write very generic, abstract, polymorphic code using these constructs. It's not any languages fault if you write your code expecting only concrete types. By this standard it's Javas fault if the developer isn't using generics. No it's not it's the developers fault. The mechanisms are there. Use them.
Having said that, I dislike articles like this because they shout too loudly. Just use a proper hybrid OO-FP (ala F# / Scala etc) language and be done with it. These languages are designed for business productivity - not academic box ticking. Everybody happy.
I was exposed to functional programming my first year at university (it was used in the introductory courses) and quickly noticed that contrary to the hype (similar to yours), functional programs tended to be more bloated with trying to work around limitations of the less expressive programming model/language and not particularly more robust.
That doesn't mean certain aspects can't be nice to have ('let' is kinda nice), but even that is mostly for programming-in-the-small.
Your unsubstantiated claim of 3x productivity increase is, er, "interesting".
In other words, when you first tried a functional programming language you tried to write imperative code in it, and the result ended up bloated and fragile? I'm not surprised.
Guess what, trying to write pure FP in Java by chaining together static methods and "Function<A, B>" objects would end up with bloated and fragile code too; but that's not a valid criticism of OO.
Here is the truth: in a few years, you'll realize that OOP is actually vastly superior to FP. It's okay if you don't believe me right now, you haven't progressed enough yet. Maybe you will, maybe you won't. If you succeed, you'll look back on FP and you will wonder how you could ever tolerate such an inferior programming model.
Always looking for the next step on the journey. (It isn't Go)
In my opinion OO languages popularized the idea of having separate internal and external representations by providing language constructs that made it practical. But they also promoted the coupling of data with the methods to manipulate it -- this is not a necessary characteristic of OO but it is common in popular implementations. This association of data and methods turned out to be a limiting factor in the face of changing (maybe unknown) requirements. The flexibility of more dynamic run-times that allow to mutate this relationship during execution (monkey patching) was not a satisfactory solution as it inhibits static analysis. In my experience this is generally the main motivation when looking into alternatives.
Modeling computations as dynamic data threaded through a series of statically defined transformation seems like a sensible solution to the issue. It also brings additional benefits (e.g.: easier unit testing) and makes some constructs unnecessary (e.g.: implementation inheritance). This approach is commonly used in FP languages and I think is the main reason why they are contrasted as alternatives.
Since it's not always possible or desirable to re-write a project, sometimes the technique is dismissed because it is confused with the languages that favor it. The relative lack of resources explaining how to use FP techniques in OO languages doesn't help either.
Separating the techniques from the implementations has practical value and it allows evolving existing bodies of work.
When the innards basically are laid bare by setters, you lose a lot of control of state and flow and object lifecycle that really hurts good design.
Looking at objects that need some kind of validation run on them, in many cases the validations aren't run every mutation but rather on some kind of save or persistence event, long after the validity of the object should have been checked.
OO can lead to great design, and there are some great techniques in FP, but average software written with either is probably terrible.
In this discussion of major programming paradigms it is important to realise a couple of things: That this is very old discussion and that there are no clear winner. The productivity and usefulness of a language is ultimately shown when it is put to the test of big practical development projects. Currently business is dominated by the object-orientated languages (C#, Java, JavaScript, Objective-C, C++ etc.) and probably for good reason: The dominant problems that software spends its line count on seems to be things like user interface and interface to other "platforms/paradigms" such as relational databases, web services or data files. Object-orientation have arguably shown itself to solve these problems well.
Why was OOP considered a good idea? Before OOP, programs were just structs and functions that operated on them. But as programs got larger, that approach broke down. Someone on a team of programmers would create an instance of a struct, but would initialize it in a way that some function (written by a different programmer) would regard the struct as having an inconsistent state. Or some function, which was meant to only be a helper function for other functions that were the API, would be called directly by some ignorant and/or lazy programmer. The result was increasing chaos as programs got larger.
OOP was viewed as the fix to that kind of problem. It was somewhat successful as a fix, too.
So the problem with most of these pro-FP articles is that they show you small examples. But the pre-OOP style worked fine on the small examples, too. In fact, almost any style works on small examples.
And showing that I can write the same thing in fewer lines by using FP isn't the answer, either. It's like saying that your algorithm has a smaller scaling constant than mine does. That's fine, but first let's talk about whether your algorithm is O(n^2) and mine is O(n log n). That is, if your approach scales worse than mine for large problems, then showing me that your way is more efficient for small problems completely misses the point.
But to build a convincing case for that, you'd have to do something like having a team of competent OOP programmers write a large-ish program (say, 10 person-years of work), and have a team of competent FP programmers write the same thing, and report on the results. Oh, yeah, and have the teams maintain the programs for five years. That could be a convincing paper, because it would address the actual issues.
Couple of notes. First, have you seen what real-world large-scale OO looks like? Dude, it ain't pretty. We're swimming in projects that are staffed at 10x or 20x the number of coders they probably really need, and a lot of time is spent in support activities.
Second, maybe the real question here isn't one of scale, it's how the model falls apart under strain. A good encapsulated system should offer you a bit more defensive programming protection than an FP one. But if you're using compsable executables, while testing at the O/S level? Meh.
I know why we went to OO. And let's not forget OOAD, a beautiful way of joining analysis and design about the problem with the solution itself. I'm of a mind that OOAD might live on even once the world moves to mostly FP.
I think maybe that OOP was a stopgap position; a place to try to build a firewall against the encroaching overstaffing associated with Mythical Man-Month management. Now that programming is a commodity, however, we're seeing diminishing returns. Don't know.
I am much less convinced of the "We need OO for large scale projects" argument now than I was two years ago. I expect this trend to continue. We might be able to solve the scaling problem with things like cyclomatic complexity limits on executables, or DSL standards. Not sure of the answer here, but I think we're going to find out.
http://simontcousins.azurewebsites.net/does-the-language-you...
The same application written in C# and F# by the same programmers (the C# project took 5 years, the F# project 1). It in no way proves anything, but it's food for thought.
Personally I've had huge wins moving from OO to functional (Haskell and F#).
Then the article goes on to show some toy example solutions in FP style, without really touching on the challenges that don't show up until larger problems.
I can write a great Account balance summer program in Python OO too, and it'll be pretty, simple, and readable...
There's even a footnote referencing Kay-style message passing OOP, but it suggests that message passing languages are not "available today in the mainstream". There are several major OO languages today based on message passing, so I don't know how that claim is justified.
Absolutely fantastic libraries, incredibly easy to write high-level abstractions over very low-level C code, and completely useless outside of one platform.
I wish projects like GNUStep would get more love.
I was at a Code Camp a few years back where one of the speakers was introducing F#. He was looking at a map function or some such on the screen and muttered something like "Well, you know, you can see the C# this compiles down to. It's all just a while statement. So there's really not much here."
At the time, I was concerned that he missed the point.
It's tough moving from 20 years or so in OOP over to FP. Whatever you do, you don't want to give folks the impression that you're just a fanboy for some new latest whizbang tech eye candy. Yet it's important to convey that there's something different going on here too. Yes, it's all just ones and zeroes, but while true, that observation is not important.
You reach a point where you say "You know, an object is just a function with closures. A function is just an object. It's all the same"
Yeah, it's all the same. But it's not. Just like that C# guy, you understand that at the end of the day all we have is state machines, but you missed the part about how thinking about things in various ways is better or worse for creating certain types of solutions.
This author tries to make the case for FP by taking apart the wild and wooly world OOP has become, where you're not just coding a solution, you're creating a extension to the type system for your very own stuff. Very cool stuff. But I think once you go down that path, you're arguing the wrong thing.
Thinking about problems in pure FP leads to small, composable functions. These group into small, composable executables. These can be scheduled and pipelined across many different O/Ses, networks, and machines. This land of unix-like coding has a multi-decade track record and solid tools that any administrator can use and understand. Thinking in terms of object graphs almost inevitably leads to very complex and nuanced solutions, with lots of hidden dependencies, that only work under certain conditions, and where you may need an expert or two around to help out when things go wrong.
FP is not nirvana. Nothing is. But it is very refreshing to have it solve the same old problems in a much less complex way. I don't see any future except for pure FP -- although my money says it might be 20-30 years until the industry catches on. Now's a good time to get an early start!
Unix-like tools such as grep (or ghc) are very much like pure functions: programs that accept input, and produce an output data. It's not surprising that they lend themselves well to FP techniques. But other programs, like the web browser I'm using now, have lots of "inputs" and "outputs." There's many knobs that can be turned, and output goes to screen, disk, network, other programs...
I suspect these programs have a larger essential "hairiness." grep only has to search text. But Find in a text editor has to show a progress bar, cancel button, intermediate result count, etc. These features are intimately intertwined with the algorithm itself, and that's often hard in FP. Try writing a Haskell function that does text find/replace with live progress reporting. It's not easy, and it ends up looking a bit like Java.
Note that the land of unix-like coding isn't very good at UIs either!
I'm finding that FP tends to shave the "hairiness" off things, many times in ways I had not anticipated.
UIs are a completely different animal. I've done a lot of UI stuff in the OO world in the past, and some in C/C++. The couple of apps I wrote in F#? I ended up doing a kind of functional-reactive thing. I really like the FRP paradigm for UI work, but I need a lot more experience to say anything useful about it. One of the things I started doing was setting up derived types from Win32 objects. Looking back, with that kind of attitude I was probably headed down the wrong road.
A web browser, eh? that's very interesting. One of my projects does some screen scraping. I found that scraping could be done in a pipeline -- get the page, score the sections, run some rules, do some QA, etc. Each stage did some work and left things for the next stage. But, of course, I was processing many pages at the same time. Rendering one page for a user sitting in front of a screen is a completely different scenario. I think.
Writing a pure FP browser would be a hoot.
Ah, I suspect you're not familiar with things like conduits/pipes/iteratees/etc. These allow you to express and compose those kinds of dirty things that you're looking for--incremental input/output, progress reporting, etc. So you could write a text search function, compose it with a progress reporter that reports how quickly it traverses input, and then report the output progressively, and dispose the whole thing at any time. Haskell's type system is fairly awesome at letting you do things like this, enough that people are still discovering neat tricks decades later.
Somehow nobody complains about this when coding in Erlang. Now do the analogy of actor ~ object.
Besides, why do you think that it's easier to think about large function composition graphs rather than large object graphs?
> "Here, I refer to OO concepts as available today in the mainstream. Initially, objects were planned to be more like autonomous cells that exchange messages, very similar to what the Actor model provides. [...] "
There is absolutely nothing in modern OO languages (Java, C++, C#) preventing you to design systems as if object instances were actor instances with method invocation corresponding to message passing. His text criticizes therefore the wide-spread (mis)understanding of OO, including his own.
Second, he equates "concurrency" with "shared memory, mutation-based concurrency". Well, there's message-passing too, and it works perfectly fine in OO programs.
---
His problems with OO stem exactly from the reductionist approach of OO=encapsulation+polymorphism. If you make the object ~ actor conceptual jump, you'll suddenly get a new perspective on how to use objects in program design.
(In the actor model, actors do not share state and are conceptually immutable. However, there's a "become" operation which the actor can use to change its future behavior on incoming messages, in effect giving you means to implement a memory cell -- not that you'd really want to do it.)
First of all the article fails in a few points regarding OO programing.
Single dispatch is not a synonym for OOP, Common Lisp, Dylan, Julia are all examples of languages with multiple dispatch.
Second, unless Erlang, Miranda, Caml Light, Standard ML, Scheme, pure Lisp are being used as examples of FP, most certanly the language will have support for some type of OOP.
The only difference is if the language is functional first or object first, concerning which paradigm is usually the one to reach to first.
I guess we need to have a few blog posts with UML examples mapped to OCaml, F#, Clojure, Common Lisp, Haskell.
At very least it would make these comparison posts focus pure FP languages, I guess.
Yes, every concept in OO can be accomplished without OO and, in many cases, are reduced implementations of more general concepts, so polymorphism is a simplified kind of type-based function dispatch. The power comes in the consistency and in the simplification itself. Instead of having multiple implementations of polymorphism that have to be developed and managed by multiple teams, you have one that is developed and managed by the compiler and understood by everybody who writes the language.
Java was very opinionated about certain aspects of OO, missing the mark of why OO is valuable in some places. It also made some very poor decisions in the implementation of its VM (int/Integer, ==/.equals) that made things worse. Finally, living in the Kingdom of Nouns[1] just sucks. But this feels like a lispers rant against Java more than a true critique of object oriented programming.
1. http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom...
Say I have a class with five methods on it. I realize I need to create a new class that has almost the same behavior: I want to reuse four of these five methods but the fifth one is different. This is a very common problem.
This is trivial to do in OOP: create a base class, override the method whose behavior you need to change, done.
I've never found an FP language that makes this as elegant.
Object-orientation sucks for everything except user interfaces. If you don't believe me, try writing a UI library without objects and see what happens. UI is OOP's best (and only compelling) use case.
For all other algorithms classical data structures/ADTs are much better. If you don't believe me, try writing an OOP compiler.
("Intensional" means "identity determined by structure"; "extensional" means "identity determined by behavior". You seem to use them in the exact opposite sense.)
I feel I should learn OO properly and maybe try Java or something where you have to code OO. On the other hand I also feel I should go with my gut feeling, forget about OO and just learn FP instead. It feels wrong though to "skip" OOP seeing most serious programmers seem to have a background in it. What do you think?
Interfaces are very heavily used, along with dependency injection (boy, do Java programmers love their dependency injection).
Inheritance is now widely discouraged. "Java Beans" are everywhere, which are glorified structs breaking every single rule of encapsulation.
Java runs fast, is garbage collected, has great support for multithreading, great Unicode support, and many other advantages. But it's certainly not very object oriented, especially in the Alan Kay sense.
FP languages have different ways to inject new behavior. You could for example define a function a -> (a -> Int) -> Int. This function now works for any type for which you can also provide the "interface" a -> Int.