Intro
Hey all.
I've done a lot of reading lately on Object Oriented Programming (OOP) and Functional Programming (FP). I have a question to ask and have some ideas to share to get the conversation going.
I know the benefits and typical code for each approach. I do want to move to more FP, to make my code more modular and to be easier to test and debug. I need advice on how.
Question
The question I have for you, is how do I take code that I have as a class with methods and rewrite it as functions? For current projects and for any new projects where I am tempted to use OOP but choose to use FP.
How do I setup my code with all functions and no classes - just so it looks good technically but is a way that feels natural/smooth and DRY, including when applied to large projects.
In one of my projects, I have a class which takes some variables on initialization and then some methods which use those initial values on the object and sometimes more arguments and then return something.
My ideas
Here is how I might do calls in OOP to setup an instance and then call a method which adds a given amount to values in the class.
foo = 1
bar = 2
myObj = new MyClass(foo, bar)
myObj.add(100)
myObj.foo
// 101
Many function arguments
Should I take the arguments out of the init/constructor method and rather make them the first arguments of each function? An argument I've seen against this is that the code becomes verbose - especially if you want to pass an argument down several calls and then the top-level function has to take everything.
foo = 1
bar = 2
add(foo, bar, 100)
// Returns [1, 2]
Pass key-value pair
I had another idea where the first argument of the function in the FP approach is an object like a dictionary / hash / associative array. So instead of passing the same bunch of arguments to each function, you create an object with those values on it an then pass the as a single parameter to the function.
e.g.
x = {foo: 1, bar: 2}
add(x, 100)
// Returns {foo: 101, bar: 102}
Use a type
In TypeScript, you can even use an interface so it doesn't matter whether the object coming in is an associative array or a class instance, as long as it has the expected key-value pairs then the compiler will run. I've read about something similar I think for types or structs in other languages Haskell, where you guarantee at compile time that the object you pass in will have the necessary keys.
e.g.
interface myType = {
foo: Integer,
bar; Integer
}
Then the function add
would only accept argument x
if it matches the structure of interface myType
. Or maybe it is a type rather.
Bad ideas
I thought of some approaches to avoid.
Using global variables magically called in functions.
Or using functions define inside functions (common in JavaScript, maybe because the class
keyword came to JS very late). I hate this style because you can't use the inner function without the outer one called first, so it is like a class and methods but more chaotic for state and unit tests.
Conclusion
Please let me know your thoughts. By the way my background is Python and JavaScript.
Thanks.
Credits
Cover image by @hishahadat on Unsplash.
Top comments (3)
Classes are not the problem. I would focus on the methods, do they behave like pure functions? Refactor those that don't.
For new projects you might want to start making a dedicated space where you have all your pure functions. The idea here is that you have a place where everything is pure and nice, and another where you can bend the rules a little bit.
Thanks, good point. Refactoring the methods that do too much and are hard to test will help achieve some of my FP goal while still using classes.
My concern is that I will fall into OOP thinking and pitfalls by using classes, so am hoping to change the mindset for new projects.
One of the bad things I've about OOP is developers tend to put way too many methods on a class it makes sense conceptually to put the methods for updating employee and representing employee in the same class. But these are separate responsibilities and so should be in different modules and also should not depend on each other and should rather be dumb - just processing data they get.
I like the idea of pure functions in one section and less pure in another. Like where the IO gets handled.
Thanks for your input.
Yes I had thought about currying as well - Python has the
partial
function which helps with this. I realize that currying with two functions is like calling the constructor and then a method, so it sounds like the FP way of handling the initial values