For me functional programming was just like a “missing link”. Wait, I can just code like I think, not in this weird statefull way? It’s just so practical for me. I can deliver 10x the value for 0.1 the effort. It’s just not fair, I know for a fact that there are more people with the same model of thinking, I just want them to feel as liberated
Same as you and GP.
I'd add that, for me, "functional programming at the edges" is sufficient: I don't need to go full straight jacket / Haskell-style to get a huge lot of the benefits of FP. I can still use an imperative outer shell and still have lots of functional parts in my code which are easy to reason about.
Can you? Like say you changed you sum() function to only sum every other element, because for the code you're working on right now that made sense. Well now you've screwed up the other places which relied on sum() summing all the elements.
Dumb example because you'd know better, but surely you can still shoot your foot off like that with FP?
And yeah I know that's not the side effects you were talking about, but when I'm changing my OOP stuff, that's the kind of side effect I'm most worried about.
I've never really used a proper functional language though, so it's certainly possible I'm unenlightened.
I would not change sum but just filter out every other element and feed that new list to the sum function. (If you need it often, write a new helper.)
Your sum function should not decide which elements should or should not be added, that is the callers job. It doesn't even have the context to decide on that.
So yes, I would have the guarantee that nothing would break because I did not change sum in the first place.
In practice you probably wouldn't even start with that sum function but simply implement a function that takes two numbers and adds them together. Then the caller can use higher order function like fold and filter to do whatever it needs.
(Of course we assume it is not literally adding two numbers but some complicated logic, otherwise don't even write a helper for it, just sum your stuff when you need it.)
And yes, you can still have logic bugs in functional programming languages and it can't really protect yourself from that when refactoring but I wanted to note how keeping your design simple and having easy to understand composable functions can help avoid bugs.
Of course, and I would do the same in my imperative program. A dumb example like I said.
> Then the caller can use higher order function like fold and filter to do whatever it needs.
Right but then at some point you have a chain of ten of these calls, and you have it _all over_, and so you figure hmm lets make a separate function out of that, code duplication is bad after all.
And then you find your new function has a bug and you need to change it... Will all the users of this new function be OK with that bugfix?
If you don't wrap up these chains into new functions, how do find all the places you need to change once you need to make that bugfix?
> having easy to understand composable functions can help avoid bugs
Sure this I get, which is why my imperative code also contains lots of "do one thing" methods that is used as Lego bricks. And I use a fair bit of functional-ish code, ala LINQ, where it makes sense.
I wish I had discovered functional programming at an earlier stage, where I had more time to experiment. I think it would be very informative to make two non-trivial feature-equal programs in either style so I could compare.
One of the ways I finally learned that was after learning async/await in C#. Every beginner to async methods dread in fear once they realize the "zombification" of the Task<T> return type, where if you call an async function 6 layers deep into your program you need to change the return type all the way up the stack. Almost always now I call the async function at the very top layer. If I need some value or list I compute that list or value and return that all the way up then make the async call at the top based on it.
I learned to split my functions into two types, pure functions and impure functions. Async functions are an example of an impure function. The only thing those functions are allowed to do is their impure thing. Make a web call, push a value into a database, whatever. The pure functions are where you actually do your computations and transformations.
If you have a pure function that has a bug and you need to change it, because it's a pure function it is inherently testable. Just run it and see. If you're not sure, it's trivial to create a new function with the bugfix and only call that function from the places that you are sure of. But then try to make sure.
That's not a "side effect"[1] at all. It's a logical bug in the implementation that only affects the function's return value. You absolutely still have to worry about those sorts of bugs in FP, which is why you still need tests or a fancy enough type system to prove the bug cannot happen (for your example that is unlikely to be practical).
"Without any fear of side effects" doesn't mean you don't have to fear anything at all, but it's one less thing to worry about.
[1]: https://en.wikipedia.org/wiki/Side_effect_(computer_science)
Yes, like I said. Point is, in my imperative OOP code, I'm relatively seldom worried about actual side effects, and more worried about introducing bugs due to me not fully grasping all the interactions of the code I'm changing.
Did someone somewhere make an assumption about how this code works (sums all numbers), and am I breaking that assumption when I'm making this change (summing every other number)?
I'll give you one concrete example: In a number of ORMs, I have no idea when database calls actually happen and this can lead to a) subtle logic bugs (inconsistent state), or b) bad performance (N+1 queries).
I've had such problems both with Ruby's Active Record and Hibernate, two of the most popular ORMs. I've even deployed code that had subtle bugs, even though I wrote tests, because while my tests verified that the correct state of the object was written to the DB, the updated state was not reflected in the JSON response object. I'm sure similar things happen in other frameworks as well.
A PFP can prevent that sort of craziness from happening.
In the example you just gave (summing over a list of numbers), in almost all programming languages you'd resort to runtime verification instead, i.e. tests, just because it's so much more practical. But if you have a PFP language, you still have the confidence that the behaviour of the inputs and outputs is the only thing you have to test, since the function cannot do anything else.* Even in a non-pure FP language, you know that it cannot mutate other variables, although it might have side effects.
* Ok, it might loop forever or crash, but that's comparatively rare.
There's a certain kind of personality that really gets into that (the 'architecture astronaut'), specifically because it is counterintuitive and they like convoluted things. I think the idea that really gets them going is "look, it knows to XYZ itself!"
I do scientific programming, and most of the time, functional style makes way more sense. Doesn't stop someone with the OOP bug from turning all the logic inside out and making a brittle mess, though.
What OOP gives is a way to encapsulate some given part of a program, and make it a working little building block you can later reuse. It can contain mutable state, but is promised to be correct as only that class can touch it, it can be used as a slightly different version of a common interface, etc.
The two are not mutually exclusive at all.
Relations between Objects change, customers don't know what they want, change is always pain with OOP.
Now people say, composition over inheritance. Right, but isn't that the point of functional programming?
Functional programming maps to how I think. The most important questions is always: What data structures do I need? Get your data in order and the rest will flow naturally.
I don't use functional programming because I am a math nerd or something, I am not even good at math. I use is because it is composible and easy to understand. I can refactor without any fear of side effects.
Now is pure functional programming practical? For some tasks, sure but yeah not always. Imperative programming gets stuff done. Work with the strengths of both.
The thing you are not used to always feels weird, that is a problem with you not the thing you try to learn.