Talks

Monads as a Clean Solution To Our Messy Code

Paris.rb Conf 2020

00:00:16.360 Thank you! Hi everybody, my name is Nathaly Villamor. I've been working as a software developer for the last four years of my career, and I must say that coding is one of my biggest passions. I love to code and create solutions through programming.
00:00:21.410 I also enjoy contributing to software communities, which is why I organize events like Rails Conf in Bogota, and other meetups. I love to spend my time giving back to the community and sharing the knowledge and resources I've gained over the last five years.
00:00:34.370 First of all, I want to talk a little bit about my birth country. As some of you already know, I'm from Colombia. Could those who have ever heard of Colombia please raise their hand? Oh, that's nice! When I say Colombia, I hope you're thinking of the wonderful coffee, sports, vacations, lovely food, and of course, good developers.
00:00:55.460 By the way, I brought some coffee for you guys. If you want, you can reach me later, and if you answer a simple question, you might win fresh Colombian coffee to try! Currently, I'm working as a development lead at Waco. Waco is a Colombian company focused on creating solutions to save water. We have an IoT device that controls the water flow in house pipelines. This device allows us to open and close a water valve, provide insights about water usage, and raises alarms when there’s a pipe breakage or fraudulent activity.
00:01:28.670 This is such a great innovation because aqueducts often lose nearly 40 percent of the water they produce without being able to control it. Our tool not only helps increase their efficiency but also conserves water simultaneously. Right now, Waco is designed with the Colombian aqueduct model in mind, but we hope to expand to other countries with similar challenges in the future. We've been working on this product for about a year, starting from the well testing phase to the user interface. It's a product we built with a lot of dedication.
00:02:06.619 Now, I want to introduce my team. We are five engineers working on this project. One team member designs the hardware, while the rest of us manage the water device's software and development. Our team is composed of two junior developers, and I am responsible for mentoring them. They are currently learning the basic concepts of Groovy and SOLID principles. However, when I mention 'monads,' I can see their faces show confusion. I often struggle to explain this concept clearly, but I will address it later.
00:02:36.650 Before diving into monads, let me describe our architecture. Our application is built with a Ruby API, AWS IoT, a frontend interface utilizing React, some Lambda functions, and for certain smaller processes, we have AnyCable for data flexibility. Building everything has been quite exciting, but our most significant challenge has been the sustaining machine. It's an asynchronous process that involves all our platforms. Writing robust code can be challenging, especially from my view as the team lead.
00:03:06.000 The sustaining machine is crucial because it controls the process of opening and closing the device's valves, which relies on numerous variables such as the real device's state and counters that keep track of how many attempts it has made to change a given state. Our system interacts with several AWS IoT endpoints and others, so we created a hash to simulate different objects that can be affected based on the context.
00:03:41.930 For instance, there are five main processes we need to execute when changing an office state, depending on certain conditions. These conditions are based on the variables I've mentioned. I want to point out that the code snippet I will show you only works when we want to open the valve. In reality, we have numerous scripts like this to refactor. As you can see, it looks fairly simple; however, it's fragile. We need to replace this hashed code, as it can fail for several reasons.
00:05:06.560 To address this, I wrote a sketch using my favorite monad, 'Result.' It's nearly impossible for me to conceptualize a solution without considering the 'Result' monad. I started explaining to my team that the 'Result' monad is essentially a wrapper for responses in the system. I used an illustrative image from a DITA Merkava, who has a helpful article on this topic.
00:05:39.300 Like DITA, I explained how monads work while avoiding explicit definitions. The general idea is to wrap responses in boxes, which are then tagged with data types based on the specific monad. I'll explain this in more detail later. I also mentioned other essential concepts, such as functors and applicatives, among others, but for now, we can focus on monads.
00:06:00.680 Then I shared several complex articles about monads, which I must admit were challenging to read. With every read, I realized just how little I knew about monads. So, I resolved to simplify these concepts and incorporate gamification techniques to introduce each monad in a way that resonates with my team.
00:06:39.080 Let's imagine that the mission of developers is akin to a game, where we have to build solutions as our main goal. In every game, we have powers, which, in my analogy, represent monads. I created a fun concept called 'Demon Explorer,' which I threw together using elements inspired by Pokémon for illustrative purposes. This way, it would resonate better with my audience.
00:07:47.330 In my game, I designed a labyrinth with various paths, but only one leads to the main goal, which reflects the happy paths we often write about in our code. Other routes represent potential errors, which reflect how user stories can unfold in unpredictable ways, taking us off course. So, just like in a gaming environment, we need tools, or powers, to help us overcome obstacles and achieve our victory.
00:08:43.330 The first tool I introduced is the 'Result' monad, which I find immensely straightforward to use. I refer to it as the level one power. As you navigate the map and discover two paths, the 'Result' monad can assist you in determining which path to take by utilizing two data types: success and failure.
00:09:12.950 By wrapping the response in a box and tagging it as a success or failure, I believe this process becomes significantly easier to manage. Now that we understand the 'Result' monad, we can progress to level two. Here, we often encounter a 'monster' represented by methods that could return nil or unexpected results.
00:09:52.510 This scenario may lead us to add multiple conditionals, creating a tangled web of checks and balances. To address this, we use the 'Maybe' monad to wrap each process, which allows us to return either a value or nothing, depending on what the original methods return. Here is an example: I'm attempting to obtain a device from a repository, which could be a database or another source. If the device exists, a 'Maybe' monad will handle it appropriately.
00:10:55.250 In this manner, I can transform a 'Maybe' into a 'Result,' understanding that nil represents an error and the 'some' tag represents a successful response. This approach is helpful because it keeps error types organized, making it easier to manage the workflow without excessive lines of code.
00:11:49.200 In my second example, I'm asking a device repository for an update. The 'Maybe' method can return either an updated object or nil. Let's continue advancing in our understanding of monads.
00:12:20.000 Now that we know how to use 'Result' and 'Maybe' together effectively, we can introduce the third level, which involves the 'Try' monad. At this stage, my team began to grasp the concept of monads in a general sense. However, I have to admit that my imagination was becoming exhausted.
00:12:49.580 Let's say the 'Try' monad represents a final enemy. To use 'Try,' I only require something that may generate an exception, which can easily be illustrated with examples from AWS IoT. I crafted an exception specifically for this demonstration, which showcases how to wrap a process and manage exceptions effectively.
00:13:26.210 Using this approach, we can control business requirements and change them as necessary by simply altering dependencies. So, what happened to our conditionals? Well, they still exist! I utilized pattern matching to create an object that holds different instructions based on the conditions that need to be met.
00:14:04.540 When it comes to testing, we can validate each module independently, creating tests that encompass various conditional scenarios. If any dependencies change, we can manage these updates seamlessly.
00:14:44.479 Unfortunately, our project is built in Ruby 25, so when I return home, I'll have to remove the pattern matching, which is unfortunate. Previously, I mentioned error handling. You can manage errors based on the error object sent to the monad's class. You can route your result through an error handler method, utilizing pattern matching to define how each type of error should be resolved.
00:15:17.330 Moreover, you can integrate other moments, like maybe, to specify values and manage answers accordingly. In essence, you can create as many paths as you need in your coding journey, which, in my experience, is incredibly useful. This structured approach offers a reliable way to understand how a piece of code may fail and how those errors can be managed.
00:16:20.170 If you incorporate this strategy in every new feature, you’ll find your code more legible and understandable. I must note a few pros and cons of this methodology. On the positive side, we can clearly delineate the processes we are undertaking, as well as adjusting dependencies along the way.
00:17:01.720 Additionally, error handling becomes simpler since you don't need to navigate across multiple files to find various conditionals. However, I haven't yet reviewed the performance of this structure, so that's something to consider.
00:17:58.080 Before trying to use monads, you need to understand some core concepts. It's a steep learning curve, but grasping how they function enables you to leverage their full potential. This experience enlightened me on how I could create visuals that simplify understanding.
00:18:30.580 As a parting thought, Eric Meyer has a great definition of monads, stating that they are return types that guide you along the happy path. I resonate with this statement firmly. This is my team; each member plays a vital role, and we continually practice how to utilize monads wisely.
00:19:01.770 Although I couldn't provide every concept in this example due to complexity, each monad serves a unique purpose in different situations.
00:19:06.769 To wrap up, applying the power of monads can help alleviate nested callbacks and enhance code usability, but it's crucial that the entire team understands how they operate. Documentation is key for newcomers, so everyone benefits from this shared knowledge.
00:19:32.290 I've managed to explain monads to my team in an accessible manner, and they collectively agreed on a strategy to employ them while maintaining clean and organized code.
00:19:46.300 In closing, don’t force monads into every single scenario—they're not a magical fix for all problems. I've drawn on insights from various texts, including a preamble by Vertical, which also resonates with how we embrace the use of monads.
00:19:58.919 To summarize, consider using a monad when the value may or may not exist, when dealing with values that could produce errors, or when the value depends on external states or contexts. In conclusion, you should utilize monads whenever you see fit, like utilizing Ramona’s or finding different implementations aligned with your needs.
00:20:41.740 I invite you to explore all available resources and documentation related to monads. This is indeed a complex topic, so take your time with it, understanding that it incorporates mathematical concepts that may not be immediately intuitive. Thank you for your attention!