The trick to sane OOP is doing as little of it as possible. When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well.
The trick to sane OOP is doing as little of it as possible. When you maximize the number of pure methods, minimize mutable state, minimize inheritance and so on, you are doing OOP well.
But when you write that style of OOP, what's the advantage over a non-OOP language that is immutable by default, uses just functions, and provides constructor (in the FP sense) hiding to maintain invariants?
To me, it seems that it only has disadvantages: team members can still write mutable, reference/pointer leaking horrors.
There are deep principles that overrule paradigms and they're rarely explained via OOP.
Logical regression over function yields combinators. (like how one derives Y from factorial)
Thinking in terms of properties instead of memory, unless you reach interface only OOP you don't do it (OOP in the Java/UML sense gives you rope to bike shed to no end, effectful constructors, is it composition, aggregation ... a never ending well)
The notion of eta conversion, that I see as a generalized form of 'unnecessary wrapping' (if true then true else false and such) that is clean and well expressed in FP and full of bolts in Java (anonymous classes are too verbose, .Net had delegates for this I believe but it's named as yet another kind instead of an equivalence).
Linked to reliance on temporaries instead of combinators
a = f ..
b = g a
c = k c
return c
instead of simply
(k . g . f) a (or a |> g |> ... for reading order )
ps: I encourage people to read books like Programming in Haskell from Graham Hutton (this book deals with average problems, not too sophisticated alienating ideas) to get a feel on how so many things can be re-expressed as composition.I think there is no advantage to using an OOP language with mutability by default, over not doing so. (The only advantages are circumstantial -- the most common are probably that the existing code is OO, or that the existing devs are OO, or any devs we can find are OO, or the company is a shop that has always done OO, etc. ).
I didn't say that all state should be immutable. Sometimes you actually must have mutable state (e.g. in a large scale game engine or scientific app where performance and cache is king, you likely do want to modify in place, which is hard to do with immutable abstractions over the data). The trick is to use as little mutable state as possible for the task at hand.
I certainly hope most new statically typed languages will not use mutable by default. Rust is a good example. Rust does most of what OO and FP does well, but does it without actually being either FP or OO in the normal sense.
which after a while, I realised, was just functional programming with more boiler-plate.
* Thin wrappers that slightly change functionality are often a business requirement, and "extends" inheritance provides an easy way to express them
* Having data associated directly with the corresponding state can make exploratory programming easier (e.g. suppose I have a library for accessing the Facebook API, so I fetch my user info. In OO style it's immediately clear what I can do with my user info, because it's all methods on the object; in functional style it's less clear)
I doubt that a business requirement stipulates how something is implemented, but thin extensions are often an apparently convenient way of quickly getting the slightly different behavior of the business requirement. Whether or not it's a good idea just because it is quick and convenient I think isn't clear. From a business perspective this is one of the false benefits -- if the problem is apparently solved quickly, but the maintenance is harder afterwards, it may not be the best idea. I'd bet sum types + common pure functionality accomplishes the same thing with lower maintenance cost.
> Having data associated directly with the corresponding state can make exploratory programming easier
I agree that many functional languages have definite drawbacks in terms of exploratory and self documenting programming. "Methods" on types are to me a definite requirement also for an FP language because of autocomplete/documentation/ergonomics. To , me writing v2 = Vector.normalize(v1) is vastly inferior to v2 = v1.normalize(). This is syntactic sugar of course, but important sugar. Neither is "OO" though: associating methods as postfix can be done regardless.
Not my experience - most functional languages make it much harder than it should be to say "this is exactly an X except that it prints differently".
> I agree that many functional languages have definite drawbacks in terms of exploratory and self documenting programming. "Methods" on types are to me a definite requirement also for an FP language because of autocomplete/documentation/ergonomics. To , me writing v2 = Vector.normalize(v1) is vastly inferior to v2 = v1.normalize(). This is syntactic sugar of course, but important sugar. Neither is "OO" though: associating methods as postfix can be done regardless.
Well, if a feature is present in all mainstream "OO" languages and not in any mainstream "functional" languages then I'll call it "OO". I'd be very interested to see a functional language with good support for this kind of thing though.
yes, FP usually makes it harder. The question is whether the extra effort is worth it over a 10 year period or not, i.e. does the FP solution maintain more easily than the OO one? I'm not saying it does, just saying the answer isn't clear after the implementation, only much later.
> I'd be very interested to see a functional language with good support for this kind of thing though.
F# is a good example.
That's because this is the wrong thing to say in the first place - it dilutes the very meaning of “type”. You should factor out the common parts, and only then say X prints this way, Y prints that other way.
You can do ad-hoc polymorphism using type boxes (existentially quantified type classes). Of if you think this is an anti-pattern, just plain old data structures plus (first-class) functions:
https://lukepalmer.wordpress.com/2010/01/24/haskell-antipatt...
Thin wrappers that slightly change functionality are often a business requirement, and "extends" inheritance provides an easy way to express them
So does composition.
In OO style it's immediately clear what I can do with my user info, because it's all methods on the object
The problem with this is discussed in the linked article.
Edit: I see that you do Scala, so all of this is not news to you ;).
I've found composition is always much more heavyweight than it should be. "Deriving" helps a bit, but it still feels very manual when your use case is along the lines of "I have this thing with 6 behaviours and now I want a wrapper that changes just one of them very slightly".
Odd, that's also how you do functional programming well.
(As regards functional programming languages not having inheritance as such, well, if you don't do any inheritance you're certainly minimizing it.)
More and more, I feel that mutable state is really an advanced topic and should be treated with the kind of suspicion we usually reserve for multiple indirection and undisciplined use of the goto statement.
This. Mutable state is the goto of the 90's. Something that linters and compilers of the future will warn us about, just like we today might have to put pure pointer arithmetic in C# inside some unsafe block because we need to express that "trust me I know what I'm doing".
Maybe...
The attempt to forbid (global) state, as written in the op, is honourable, but I think, if a developer wants state, (s)he'll find a way. There is no good code without good developers. So lets go on educating ourselves.