Forgive me, if I'm being dense, but doesn't either of these cases depend on how the composed objects are being used?
In your functional example A is an input to B (or vice versa?), how do you propose testing one of the modules without first instantiating the other one?
I'll give you two examples. One functional and the other OOP. Both programs aim to simulate driving given an input of 10 energy units to find the final output energy.
#oop
engine = Engine(10)
car = Car(engine)
car.drive() #result 8
class Car:
def __init__(self, engine):
self.engine = engine
def ignite(self):
self.engine.energy =- 1
def run(self):
self.engine.energy =- 1
def drive(self):
self.ignite()
self.run()
return self.engine.energy
class Engine:
def __init__(self, energy):
self.energy = energy
# ignite not testable without engine
# run not testable without engine
# drive not testable without engine and a car
# ignite, run, and drive are not modular cannot be used without engine.
# engine testable with any integer.
# Car useless without engine
# engine useless without car
#functional \
def composeAnyFunctions(a,b):# returns function C from A and B. See illustration above.
return lambda x: a(b(x))
def ignite(total_energy):
return total_energy - 1
def run(total_energy):
return total_energy -1
drive = composeAnyFunctions(run, ignite)
drive(10) #result 8
# compose testable with any pair of functions
# run testable with any integer
# ignite testable with any integer
# drive testable with any integer
# all functions importable and reuseable with zero dependencies.
# input_energy -> ignite -> run -> output_energy
"I think the lack of reusability comes in object-oriented languages, not functional languages. Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle." - Joe Armstrong
you don't necessarily need the car or engine to simulate the energy output of driving.
I've been using static methods in java that follows the pure function way, it proved very easy to maintain even to those who inherited my code later on.
That's mainly just namespacing. The only point to use an Object in object oriented programming is to unionize objects and state. To combine them together into a single primitive. This combination breaks compose-ability.
Static functions avoid state. You put them in an object in java because java has to have everything in an object. In any other language these would just be top level functions namespaced into a package or something. You are basically using java in a more functional way. Which is fine.
Thank you so much for a concrete example. I need to think about this some more. Clearly the code make sense, but in a wider context, can you have a banana without a jungle? I'm dabbling with some functional programming but I definitely have more experience with oop, so what you're saying is difficult for me to grasp, but the benefits are hard to ignore.
There are downsides to FP as well. I am not advocating one over the other. But there is a concrete theoretical reason why FP is more modular, reuseable and organized than OOP code.
Smalltalk is possibly the only OOP language that lets objects be compose-able and modular. Check out Pharo if you're interested. If you learn smalltalk well enough, you could apply its principles to traditional OOP languages and gain the modularity benefits.
But other than that, is there any other concrete downside?
For using Pure Functions, I don't see any downside to this. Aside from it being impossible to use for outside the program side-effects like IO to device.
In your functional example A is an input to B (or vice versa?), how do you propose testing one of the modules without first instantiating the other one?