00:00:00.680
So you have rampant side effects all through your code, with mutable state everywhere. Enter functional programming to help you solve these problems.
00:00:02.679
I'm a developer at REA, and while there, I've worked on some functional projects in Scala as well as Ruby. I've encountered quite a few problems in our Ruby applications that can be addressed by adopting techniques we use in our functional applications.
00:00:10.160
From this talk, you should gain insight into the core principles of functional programming, parts you can apply even when you're not working in a functional language, and how to use these concepts in projects you're already working on—not just in shiny new greenfield projects. It's not as hard or complicated as it may seem, and while advanced concepts are welcome, they are not required.
00:00:39.719
We'll discuss immutability, how to manage side effects, referential transparency, and some common pitfalls people encounter when explaining these concepts. I'll also share resources where you can learn more. At its core, functional programming aims to prevent unexpected changes in your code, making it easier to reason about and understand.
00:01:12.920
We will start with immutability, which means you do not change any values; instead, you create new ones. You rely on constants, utilizing recursion and accumulators rather than loops and mutations. This way, any confusion about whether you're passing by value or by reference is eliminated.
00:01:25.479
If you've ever had the dilemma of whether to use a bang method when reversing a list, you were likely considering the implications of mutation, which can be extremely confusing. For instance, I once worked on the build of an older Java application where we added a new build task in Gradle. We filtered our dependencies to make changes and ran our contract tests which passed, but when we executed a full build, it failed because of unexpected dependency issues.
00:01:43.799
We realized, during debugging, that the 'filter' operation in Groovy actually mutates the list, resulting in the removal of all other build dependencies from that context. Our attempt to manage a shopping cart example can illustrate both a mutable and an immutable approach to calculating totals.
00:02:04.960
Consider a straightforward mutable solution where you iterate through all items in the list, adding their costs to the total. In contrast, a recursive immutable solution is more verbose and explicit, involving a base case where recursion stops and a recursive case where the function calls itself with a new argument each time.
00:02:56.240
In this example, we take a list and separate it into a head and tail using the splat operator, commonly used in recursive functions. Our base case returns the cost of the item when the tail is empty, while the recursive case accumulates the costs of the rest of the list.
00:03:29.039
Although this method is more verbose than its mutable counterpart, we can streamline our approach using higher-order functions, which take other functions as arguments. Using the inject method, we can apply an accumulator that starts at zero and adds each item's cost as we traverse the list. This approach replicates the functionality of the previous method but in a cleaner manner.
00:05:02.360
Moving onto side effects: these occur whenever our code interacts with the external world, such as printing to the console or making network calls. In functional programming, we strive to make these side effects explicit and separate them from the core logic. This means our core functions can operate smoothly, while the outer edges handle side effects.
00:06:00.400
For example, to add a feature to print the total from our shopping cart example, a straightforward approach might involve directly printing in the main function, which mixes logic with side effects. A functional solution, however, would separate these concerns, leaving the core logic intact while adding an outer function that handles the printing of results. This separation not only clarifies our intentions but also makes testing significantly easier by allowing us to test the pure function without the complications of side effects.
00:08:03.680
When combining immutability and lack of side effects, we achieve referential transparency, meaning that calling a function multiple times with the same input will always yield the same output. This property simplifies reasoning about code, allowing us to treat function outputs as values that can be substituted freely. For instance, printing the reverse of a list should yield consistent results unless we opt to mutate the list, which introduces unpredictability.
00:09:23.760
However, when mutation is involved, the same operation on the same line can yield different results, leading to confusion in larger systems where tracking the state of a variable involves remembering all previous mutations.
00:10:11.760
Common pitfalls when teaching functional programming include overly formal mathematical explanations, which may alienate learners. It’s vital to connect functional concepts to real-world examples. For instance, many have used monads unknowingly; explaining their practical use without initially labeling them can foster understanding.
00:10:49.800
With functional programming's academic reputation, emphasizing practical applications can combat misconceptions. Although one might think functional programming prohibits side effects, it merely isolates them for clarity.
00:11:14.360
While I couldn't find extensive resources for functional programming in Ruby, there are useful online courses and a book recommendation: "Refactoring Ruby with Monads." Additional resources include functional training in Scala.
00:11:59.120
As we wrap up, feel free to reach out to me via email or chat if you have questions. We are also hiring at REA, so if you're interested, please get in touch. Overall, functional programming is about avoiding changes behind your back, aiming to deliver clearer, more maintainable code that produces predictable outcomes.