Of course we need general-purpose, extensible languages. We also need motherhood and apple pie. But multi-paradigm languages are usually mistakes. They aim to give you the best of multiple worlds but frequently wind up giving you the worst.
We saw this with C++, which was supposed to be a structured / OO hybrid. The trouble was, the mismatch between the strucutred world and the OO world created huge complexity, starting with functions that might or might not be virtual. Non-virtual destructors anyone? Not to mention the fact that structured programming doesn't use GC, but proper OO programming requires it. See http://www.ipipan.gda.pl/~marek/objects/faq/oo-faq-S-3.12.ht... for the reasons why.
The problems get even bigger when you try to hybridise functional and OO paradigms:
* In OO the core concept is the mutable object. An object has an identity that persists as it mutates, and you can distinguish between two otherwise identical objects because they have different identities. Hence the distinction in all OO languages between reference equality (two references pointing at the same object) and object equality (two distinct objects that are equal in all their fields).
* In the functional paradigm, on the other hand, there are only values. Asking if two strings are the same object, or two employee records, is as meaningless as asking if two "3" values are the same object. Values are also immutable, so the idea of changing a string or employee value is as meaningless as changing "3" to be "4".
So when you create a hybrid functional/OO language you must deal with this impedance mismatch. If objects are mutable then the order of execution matters, and you have to trust the programmer to get the data flow and control flow to agree, thereby losing the big advantages of the functional paradigm. If objects are not mutable then you don't have an OO programming language.
Finally, I do not believe that "Any sufficiently large program will have parts that are expressed best in different paradigms". Claims that some domain is best expressed in one particular paradigm always seem to end up with statements of the form "Yes, but I want to do this my way, and your paradigm makes me do it your way". Sure, if you try to write Haskell in an OO way you are in for a world of pain, but that just means that when using a functional language you should architect your application in a functional way. And similarly for an OO language of course.
I spent a long time in the OO world, and I've used the best OO language ever designed (Eiffel). I moved to Haskell, and have become convinced that for most general purpose programming the functional paradigm is superior to OO. (The exceptions are in hard real-time and resource-bound environments). And the reason is that Backus was right.
I disagree here. In OOP the core concept is the object (whether mutable or not) understood as a set of data and accompanying functions which operate on it. This is where OOP and FP collide: FP strives for code reuse through universal functions which operate on heterogeneous data.
In practice in FP data has to adhere to a certain structure, which is not very different from OOP's classes/interfaces/traits after all. FP just ditches classes of objects as a construct, opting instead for non-encapsulated interfaces into generic data structures, whether expressed explicitly (e.g. typeclasses) or implicitly (duck typing).
This duality is exposed in languages like JavaScript, where class methods are just regular functions dynamically bound to `this`. A class instance is, after all, equivalent to a bunch of functions closed over its members. I.e. OOP is just syntax sugar to encapsulate data and functions.
Mutability is just a (leaky) implementation detail, completely orthogonal to OOP or FP. A class with no internally-mutating setters is still a class. A method with immutable bindings is still a method.
> If objects are mutable then the order of execution matters
Again, completely orthogonal to mutation. The order of executiong always matters:
(cons :a (cons :b '())) ≠ (cons :b (cons :a '()))
I agree that function composition is nicer than a bunch of statements (in certain cases!), but the burden is still on the programmer to get it right, whether dealing with mutable data structures or not.What FP languages get right is making side effects (including mutation) a second-class citizen, but that's completely orthogonal to OO.
Order of evaluation being irrelevant means that when evaluating
(cons 'a (cons 'b nil))
it doesn't matter whether you first create the outer or inner cons cell. Or, for that matter, whether you do anything at all at this point (you could for instance just suspend the entire expression as a thunk and only evaluate them when you try and access values within the list -- i.e. lazy evaluation).> If objects are mutable then the order of execution matters, and you have to trust the programmer to get the data flow and control flow to agree
Which is true both for OOP and FP, unless I misunderstood what GP meant with data and control flow.
In less idiosyncratic syntax, this says that the list [a, b] is not equal to the list [b, a]. That's the case in all languages (at least, it should be!).
The order of execution is about the same value being calculated along different paths.
For example, we can consider the value [write("foo", /tmp/x), read(/tmp/x)].
Here's one path:
[write("foo", /tmp/x), read(/tmp/x)]
[null, read(/tmp/x)]
[null, "foo"]
Here's another: [write("foo", /tmp/x), read(/tmp/x)]
[write("foo", /tmp/x), "previous content"]
[null, "previous content"]
These values aren't equal, so the path taken by the interpreter/compiled-code can affect what our program does!In contrast, we could follow an approach like Haskell and encapsulate IO actions as standalone values. This gives one path:
[write("foo", /tmp/x), read(/tmp/x)]
[<Some IO action>, read(/tmp/x)]
[<Some IO action>, <Other IO action>]
Here's another path: [write("foo", /tmp/x), read(/tmp/x)]
[write("foo", /tmp/x), <Other IO action>]
[<Some IO action>, <Other IO action>]
These are equal, so we don't have to worry about the path taken affecting out outcome.We do have to worry whether the chosen path will produce an outcome at all, since it's possible that a program gets caught in an infinite loop even if it's avoidable. We can remove this worry if we use lazy evaluation.
> If objects are mutable then the order of execution matters, and you have to trust the programmer to get the data flow and control flow to agree
My point is that, even in FP, as soon as you hit sequential operations (in your example, side effects), you're still bound by the execution order and the programmer getting it right (and how that is completely orthogonal to OOP or FP).
Order-dependent sequential operations are the programmer's responsibility regardless of paradigm.
I don't think OOP is inherently sequential. Your post made me realize GP is conflating OOP and imperative programming and his post makes sense if :%s/oop/imperative programming/.
You've missed OP's deeper point: even if immutable, the duality of object identity & object state remains. (Immutability is not the central issue. The issue is that in FP you can use the term "data" without blushing, where as in OOP "state" is masquerading as "data".)
What do you think prevents OOP languages from implementing automagical non-referential equality by data traversal by default like FP languages do? Is it some property of OOP itself? IMHO it's not a property of the paradigm.
The duality is still present in FP languages, it's just hidden under a nice automagical layer.
(= (cons :a (cons :b '()))
(cons :a (cons :b '())))
=> true
Both lists are still separate memory regions. They're the same data only in a platonic way and there's nothing that prevents OOP from implementing the exact same abstraction.