Monads: Exploring Railway Oriented Programming in Ruby

Summarized using AI

Monads: Exploring Railway Oriented Programming in Ruby

Abiodun Olowode • May 31, 2024 • Verona, Italy • Talk

Summary of 'Monads: Exploring Railway Oriented Programming in Ruby'

In this engaging talk, Abiodun Olowode explores the concept of Railway Oriented Programming (ROP) in Ruby, delving into its implementation using monads. The session focuses on improving error handling in applications, simplifying complex workflows, and enhancing code readability. Throughout the presentation, scripted coding sessions allow participants to actively engage and understand the material, solidifying their learning experience.

Key Points Discussed:

  • Introduction to ROP:
    • ROP is a functional programming approach to error handling, originated by Scott Wlaschin, which uses a visual metaphor of train tracks to represent success and failure paths in an application.
  • The Railway Analogy:
    • In ROP, functions are considered tracks where the "train" represents a process; tracks diverge into success and failure paths based on the functions' outputs, invoking the use of railway switches to transition between paths based on results.
  • Function Outputs and Chaining:
    • The talk emphasizes that each function can lead to success or failure, and chaining these functions can manage workflow effectively while enhancing readability, bypassing traditional if-else error handling.
  • Introduction to Monads:
    • Monads are introduced as a means to encapsulate function outputs in a structured way, allowing for chaining operations without losing context about successes or failures.
    • They serve two primary purposes: encapsulation (wrapping results) and chaining.
  • Live Coding:
    • Attendees participate in live coding examples where simple applications are refactored to use ROP principles, demonstrating the use of encapsulation to handle success and failure outcomes effectively.
  • Structuring Code with Outcome Types:
    • The concept of outcome types is presented, where every function’s result wraps in a structure representing success, failure, or other states, making error identification and handling more uniform.
  • Error Handling and User Experience:
    • The talk concludes with an examination of how ROP and monads enhance the developer experience by providing standardized error types, resulting in cleaner, more maintainable code.
  • Final Thoughts:
    • The speaker emphasizes that using ROP and monads not only leads to improved code but also ensures the entire codebase remains composable and adaptable, ultimately leading to happier developers and better user experiences.

Concluding Takeaways:

  • ROP offers a more functional and readable way to handle errors in Ruby applications through monads.
  • The approach enhances maintainability and reduces boilerplate, allowing error handling to be managed in a single, consistent manner.
  • Utilizing outcome types results in clear messaging for end-users regarding process successes and failures.

Monads: Exploring Railway Oriented Programming in Ruby
Abiodun Olowode • May 31, 2024 • Verona, Italy • Talk

Can we apply the fundamental concept of monads in building maintainable and robust applications in Ruby? In this talk, we'll explore what railway-oriented programming is and how it enhances error handling, simplifies complex workflows, and improves code readability via the use of monads. Throughout the session, we'll write code together and actively test it, uncovering the unique failure and success contexts of monads.

Abiodun Olowode is Senior Engineering Manager @ Factorial.

---

rubyday 2024 is the 11th edition of the Italian Ruby conference, organized by GrUSP,
The event is international, and all sessions will be in English.
📍 Verona | 📅 May 21, 2024

Join the next edition
🔗 www.rubyday.it

---

rubyday is organized by GrUSP.
We organize events, conferences and informal meetings involving Italian and international professionals.
We aim to make the ecosystem of the Italian world of web development better both in terms of skills and opportunities by creating greater awareness through comparison and sharing.

Subscribe to our newsletter:
✉️ [www.grusp.org/en/newsletter](http://www.grusp.org/en/newsletter)

 Follow us
 Website https://www.grusp.org/en/
 LinkedIn https://www.linkedin.com/company/grusp
 Twitter https://twitter.com/grusp
 Instagram https://www.instagram.com/grusp_
 Facebook https://www.facebook.com/GrUSP

rubyday 2024

00:00:00.719 so I do not have um a slide that talks about me okay this works so I'm AB
00:00:10.280 that's it nothing serious um today I want to talk to us
00:00:17.359 about exploring Railway oriented programming in Ruby monad so I'll start
00:00:24.119 asking how many of us here are familiar with the term Railway oriented programming
00:00:31.320 okay so I have a lot to do I was hoping to just say I was hoping everyone would just say me and I'll be like yeah let's
00:00:55.239 now so um how do we start seeing that a
00:01:00.879 lot of us have to do this together and um spoiler we're going to code Together
00:01:06.600 live coding I know it's not the obvious choice so if anything goes wrong it's you because we're doing it together so
00:01:13.400 I'll blame you not me um before we can just the way we have oop dop we also
00:01:20.840 have r o Railway oriented programming so in the course of this talk when I say ROP I mean Railway oriented programming
00:01:28.000 I'm sure you're tired already of hearing Railway oriented programming so I'll just say Roop before we can effectively
00:01:34.920 talk about Roop we need to discuss this guy how many people know this
00:01:41.280 guy one person okay so this is Scott his name is Scott wal he's the author of the
00:01:47.920 book domain modeling made functional amazing book but interestingly he's also
00:01:54.079 the inventor of the term r o yeah I'm not going to say re oriented programming
00:01:59.920 I just said it um so what is ROP let's get into
00:02:06.280 details according to Scott ROP is a functional approach to error handling
00:02:13.200 Scott is a proponent of functional programming so I'm not surprised that he did find a way to talk about error
00:02:19.200 handling in a functional way but what has error handling got to do with
00:02:25.560 Railways what do programmers have to do with Railways let's see this is a typical Railway so let's say
00:02:34.239 we have a train that's here can everybody see track a here it's
00:02:40.120 conversational so I'll expect responses okay so this is track a here
00:02:46.400 let's say you have a train right there and the train needs to get over there track B how's it going to get there
00:02:53.599 imagine that yeah we just had one track and you could only go on track a but now you want to move to track B how is it
00:02:59.440 going to get there it's going to get there via that thing I don't want you to feel left out so I'll come here
00:03:05.280 too this thing here called a railway switch this is how the train is going to
00:03:10.640 get from track a to track B because usually it will go straight down but now
00:03:16.360 it's like nah something happened need to change and then via the switch it goes to track B what has any of this got to
00:03:24.159 do with us so Scott found a way to utilize that same analogy two error
00:03:30.599 handling and now we have two tracks a Success track and a failure track in
00:03:36.920 essence a process comprises of several functions right so let's say you have a
00:03:42.280 full process it's a combination of several little different other tiny
00:03:48.959 processes now Scott found a way to say we just the way we have switches in the
00:03:55.560 railway the outputs of each of these little functions that make a process
00:04:01.360 would be the switch and then the train would be the process are we following so
00:04:07.519 the process is the train and then the switch is the what output of all of the different
00:04:14.439 little functions that make up that process practical example this is Scot
00:04:21.600 example and as was it Alexander that said we do not need to reinvent the wheel right this is his example so I'm
00:04:28.800 just using it EXA exactly as he used it I found it very easy to understand so let's say we have an entire process to
00:04:34.639 update a user's details the three tiny little functions that make up this
00:04:39.880 process are validate user update their details and send success email in r o
00:04:45.520 this would look something like this so each of them have their tracks and if
00:04:52.840 the validate function FS the process moves
00:04:58.639 where where does it move if validate function fails remember
00:05:06.880 that the train is the process right and the switch is the result of each of the
00:05:13.600 little functions that make up that process are we following so if the
00:05:19.160 result of validate function is a failure what happens does the train stay here or does
00:05:27.880 it move here red track thank you very much red track
00:05:34.120 that's because validate failed but if it didn't fail and it was successful what
00:05:40.840 happens Perfecto so it moves over to the next and if that fails what
00:05:48.000 happens thank you so you get the point the entire process is either a success
00:05:54.160 or a failure based on the output of each of the functions that Encompass it is
00:06:00.240 that clear does everybody understand that so basically it's like
00:06:05.960 saying this process starts on the Success track and once there's any failure result it's like oops not going
00:06:12.960 there anymore you can't keep up in with code this will look something
00:06:18.479 like this ignore anything that you see that looks not normal not like something you
00:06:25.680 would write like the return email not sent it's to the code so basically oops
00:06:33.199 that went so fast basically what we have here is we try to validate the user if
00:06:39.440 there are errors we return the errors right but if there are no errors we move on to update details if there are errors
00:06:46.680 we return the errors and if not we keep moving at the end we can say it's
00:06:51.720 successful because all of the little functions that made up that process were all successful does that make sense
00:07:00.479 okay so but what did we have if this return this else this return this unless
00:07:07.759 this do this right but what do we want thank you we want something that's
00:07:13.400 readable we want something that you can look at and love you you know okay so he
00:07:19.039 was saying something yesterday about a director who saw some code and was like this code is shitty but we don't want
00:07:26.280 that you want to be able to see code and be like who wrote this code it's so so beautiful so the question is is there a
00:07:32.759 more functional way is there what is
00:07:38.599 it please clap for yourselves put your hands together you're the best crowd ever yes of course there is ROP is the
00:07:46.599 more functional way to handle errors but how so how do we Implement ROP via
00:07:54.000 something called monads how many people have heard of monads amazing so I don't need to talk about about monads let's
00:08:00.560 just go Wikipedia says a monad is a structure that combines program fragments functions and wraps their
00:08:06.960 return values in a type with additional computation blah blah blah blah for the
00:08:12.159 Nerds let's break it down two things monards are able to carry out
00:08:17.840 encapsulation and chaining so let's start with encapsulation it's like a box
00:08:23.520 so via this you can say this is the result of this function put it in the
00:08:29.199 Box it serves like a container so was it a success put it in the Box was it a
00:08:35.760 failure put it in the box so this box represents different states right but it serves like a container then imagine
00:08:44.240 that every function that makes up the process actually returns a monard what happens
00:08:52.080 that means we can change them all together monard in monard out monard in
00:08:57.320 monard out exactly and that's how we get get encapsulation and chaining does it sound as nerdy as Wikipedia actually
00:09:04.839 made it sound nah it's pretty easy but if none of the things I've said made any sense to you that's okay and
00:09:12.079 understandable let's write some code together so if anything fails it's you
00:09:18.120 get ready I can see him preparing himself so we're going to thank God we just had lunch we're going to build a
00:09:24.200 Mona to help us deliver lunch or maybe pizza to your doorstep so I
00:09:30.000 try to get something pretty easy and simple and fun for us to just explore r
00:09:36.480 o how many people are ready okay so let's do this too tiny too
00:09:45.079 big too small let me know so I'm going to start increasing should I do some more or it's
00:09:51.160 perfect okay amazing so this is what we have here
00:09:56.600 every time I went through this code I was sitting I feel sowhere to be doing it
00:10:03.120 standing okay so we have a launch class here pretty basic code what's happening
00:10:08.800 let's go through it we have an other method which um tries to send the order
00:10:15.640 of the launch so um we have valid cities and we have available meals so pretty
00:10:22.079 basic we have um return invalid City unless the address is valid so let's say
00:10:27.680 I had to add Verona to the cities that are valid so you don't chase me away um we have London
00:10:35.279 Paris Verona and the meals that are available are Pizza and Pasta I don't know why the peas it was co-pilot who
00:10:41.160 picked all the peas so please this is not me so we have pizza and pasta and
00:10:46.519 what we're doing here is return invalid City unless the address is valid return invalid meal unless the meal is
00:10:53.040 available I used invalid invalid invalid so it's pretty easy to just go through instead of writing a very long error
00:10:59.760 message but do we get everything that's happening here it's pretty straightforward and return invalid card
00:11:05.760 unless the payment is successful does everybody get this piece of code it's very easy and then we have our
00:11:13.320 spec let's go through the spec so we are describing order and when the city maill and card number are valid we expect the
00:11:21.360 that the delivery is successful right but when the meal is not valid we expect a failure when the cat is not valid we
00:11:28.120 expect a failure when the card number is not valid we expect a failure pretty simple and easy so let's run the test
00:11:34.480 let's see what happens for example zero failures everything works so now let's start r o
00:11:41.360 let's see whether we'll be clapping in the next 5 minutes so do you remember that we want types right we want to be
00:11:47.720 able to wrap the results in a container what shall we call
00:11:52.839 it I'm thinking outcome I already used outcome it's like what was the outcome
00:11:58.120 of this experiment you decided to whatever so yes results Dron ad uses
00:12:05.079 result so I'm like I'm going to go outcome is okay so let's create a new file um I
00:12:13.560 don't want to do that here so let's do it here let's call it oh this is so weird
00:12:20.160 to be bending to type is it in the right place I don't think
00:12:26.480 so okay so let's call it outcome. B and what do we want
00:12:32.959 basically so um we're going to define the outcome model here which is this nothing serious
00:12:41.040 we want types right success and failure so let's create our types let's create a
00:12:50.120 success and it will be the success class I have some Snippets that already
00:12:58.160 come up so just in case you see that don't worry soon we'll be doing everything together
00:13:04.320 so basically how many of you are used to sbet or
00:13:09.839 sbet okay so I'm actually using some people call it sbet some call it
00:13:16.560 sub I I'll call it sbet so um I'm using sbet for to type my
00:13:23.480 files it makes it easier to know where things are going wrong and we can move faster it was created by stripe so let's
00:13:29.920 say you have a function the result is going to be a value 2 + 2 4 that's the
00:13:36.279 result and it's a value so let's wrap that here
00:13:43.000 um value what will be the type we don't practically know yes because it could be
00:13:49.160 anything right we also want an
00:13:54.759 error Ty so I'm just going to copy and paste
00:14:00.000 let me call that failure so that we are failure.
00:14:06.880 RB let's come here so it's going to be a failure what property do you think failure could have when something fails
00:14:13.560 what do you expect it to have perfect though you guys are so amazing and what will be the type of the
00:14:19.920 error message okay let's talk about that later remind me should it be a string let's
00:14:26.440 discuss that later so basically now we have failure and success but we know we could also have many other different types right some we have maybe so it's
00:14:34.199 not success it's not failure it's like a maybe so basically we have outcome types
00:14:39.440 so let's put everything in an out oh my
00:14:45.000 God okay let's rename that to outcome type.
00:14:51.279 RB and let's see do I have something for outcome type yes I have amazing so in
00:14:58.880 the outcome type this is just going to Encompass all the different types that we have it could be success it could be
00:15:04.279 failure it could be whatever you choose to call yours now let's come back to launch which is why we are
00:15:11.199 here basically what we want to do is to say if the valid cities include address
00:15:18.000 is it a failure or a
00:15:23.399 success okay so we want to be able to say something like outcome do success and we give it a value so let's say
00:15:29.519 maybe address or we say outcome. failure and then we pass the error message which
00:15:35.800 is inv valid City this is why sbet is amazing it's already telling us outcom doesn't know anything about a success or
00:15:42.319 failure so let's fix that let me
00:15:48.560 see amazing so now with outcome we add success method and failure method but
00:15:54.600 what would this be remember it would be out thank you Copilot something like
00:15:59.720 this and for failure it will be something like this let's not forget the
00:16:05.839 new Ruby syntax that Lucy is it Lucy
00:16:12.839 or okay so does this make sense so we put everything in the box if it's a
00:16:19.079 success good if it's a failure good now let's go back to lunch and let's see what happened oh yeah it needs a value
00:16:25.680 right so and you guys didn't tell me that's not nice so this should be error
00:16:34.360 message co-pilot is been nice to me so let's see is that fixed amazing it's fixed right so let's replicate this for
00:16:42.399 every single method so we can go fast who's B already please don't be
00:16:49.639 amazing okay and this would be invalid meal and let's send a card number here
00:16:57.560 and this would be in valid card so let's run our test now right and let's see
00:17:04.039 what happens of course everything fails does
00:17:09.640 anybody know why so when we check is the meal valid it tells us bger successfully
00:17:15.319 dispatched to London but the meal is invalid City Pizza successfully
00:17:20.880 dispatched to Abuja all of these things are invalid but why does it pass let's
00:17:25.959 see can anybody tell me in two seconds look at it and tell me why we are all
00:17:31.120 ruby developers amazing it's returning an
00:17:38.000 object so that means we need a way to be able to say if address is valid is a
00:17:43.760 success right something like that we need to be able to check if the method is successful because now it gives us a
00:17:51.760 type but as you can see because this is not typed so doesn't know that that not
00:17:59.440 there did you see that it just immediately appeared to tell me we don't have anything like that so even though
00:18:06.360 we say Ruby is flexible typing is not so bad
00:18:11.600 no they don't agree they don't agree it helps us go
00:18:17.760 faster okay so let's see it tells us that we don't have any of
00:18:24.039 these so maybe we should do that so let's see on the outcome type we need need to be able to tell when something
00:18:30.480 is a success or a failure how do we know it's a success who can tell
00:18:37.640 me what the object okay let me type the
00:18:44.760 object so it's easy for us to identify so the object is either for now
00:18:52.240 it's either a success or a failure right amazing so how do you know if it's a
00:18:57.440 success amazing and how do you know it's a
00:19:04.520 failure so let's see now yeah let's run our test again P
00:19:12.480 moment of truth let's running from here yay everything works but have we achieved
00:19:20.240 anything so far has anything practically changed in our code we're still doing
00:19:25.400 the same thing return unless this if this do this both we have achieved encapsulation right we've been able to
00:19:32.320 put in the results in a box so the next thing would be what
00:19:38.280 amazing so that means we want to get rid of all of these things that do not help
00:19:43.559 us we don't like them we want code that's easy to read why does this keep
00:19:50.760 doing this okay so we want to be able to chain all of this together that means we're
00:19:56.919 not going to be asking is it a success or a failure we want to automatically be able to say if it's a success just move
00:20:04.600 on to the next if it's a failure do nothing so we want to have something similar to
00:20:10.679 this so we bind them together and same here do me
00:20:22.480 good do card number amazing let's have the end and
00:20:29.039 and okay so soet is already telling us that we do not have a DOT bind
00:20:35.159 right okay so and this is an outcome type so let's go to outcome type let's Implement bind
00:20:42.000 can anybody tell me what bind is going to look like Ruby developers let's go tell
00:20:50.760 me tell me tell me something like this something like
00:20:57.080 this mhm let's see if we can make it cleaner so um let's return self that's
00:21:04.840 amazing kot is telling us already if it's a failure else let's yield the
00:21:10.279 value of the object does that make sense now sub is going to complain because it doesn't know how to translate
00:21:17.559 failure to this if you're like me and you don't like seeing red I'll just
00:21:23.679 repeat myself instead of casting don't mind s it's not still as intelligent as
00:21:29.039 intelligent as it should be so let's go back and let's see what's happening with launch everything is
00:21:35.159 binding so we no longer have errors but let's let's let's run this ta three
00:21:40.559 things fail why can you see this so the only thing that passes is the first test
00:21:45.799 let's come to the first test the only thing that passes is the first test which says when the city me and card are
00:21:51.120 valid everything works the rest of them we expected the errors invalid City invalid me but what do we get we're
00:21:57.960 getting an outcome type with an error message we actually get Dem monard but
00:22:04.520 we can see that the bind works because whenever the first is actually successful it moves to the next when
00:22:10.799 that is successful it moves to the next so that means in our test we need to be able to
00:22:18.039 say where's my test file this is it I was looking right at it all the while so
00:22:25.400 um we need to be able to say something like P the subject dot success of failure something
00:22:33.480 like that since we already have that right to be true
00:22:40.840 oops is that right let's run just this one and see what
00:22:46.120 happens okay yes so we are no longer checking for the error message because
00:22:52.039 I'm going to try to go fast I'm not going to implement error message now but let's do
00:22:59.720 this so at all points when we expect a failure we're just going to say expect
00:23:06.080 that failure is true does this make sense is everybody following so let's
00:23:12.320 run our test again yay everything passes so do we get the whole idea of r
00:23:20.559 o ROP saying put in a box encapsulate and chain now is our code beautiful
00:23:27.559 let's see how does this look does it look a bit better M or
00:23:34.200 menos we no longer have return unless if and all the dances but shouldn't we also
00:23:39.880 return an outcome here shouldn't we also return a monard if every function should return Imon even though this is the
00:23:46.760 process we should return a success per se right it was successful please
00:23:53.760 someone tell me how many minutes I have more or left
00:23:59.840 MC is getting carried away with the code yes um let me check on the schedule test
00:24:08.799 F now yeah you have another like 15
00:24:15.240 minutes yay okay so we expect that subject do
00:24:21.279 success here should be true so now let's run
00:24:27.760 everything and let's see what happens taada everything
00:24:33.840 works C for yourselves you did that but but do you remember something I
00:24:42.000 said we were going to get back to yes yes how many people here believe
00:24:48.720 that we should be returning strings here at every Point invalid City invalid mail
00:24:55.440 invalid card invalid invalid invalid can we make it better in some way maybe
00:25:01.440 let's see what if we said that we should have a type and this should be something
00:25:09.600 like an error type and let's create the error type
00:25:15.559 class error type copilot help me
00:25:24.279 please yes yes soet and um how does so yes yes
00:25:31.679 yes co-pilot was going to help me there and then it stopped what
00:25:37.200 happened I would have gone faster amazing so this is how subet enms look
00:25:44.159 why is this you see this this is the oh so this
00:25:53.039 class should come from the en yes amazing so we are there so is me
00:26:00.559 red P you see this these are the issues this is why you shouldn't do live coding
00:26:06.600 now I do not remember the Syntax for enom in um sbet uh yeah and I was
00:26:13.440 expecting that copilot should help me perfect you see it was the S okay so
00:26:22.080 we have invalid City invalid me invalid card what if we just call call all of this invalid param or maybe in our case
00:26:30.880 would not supported do a good job so imagine that in your code base
00:26:36.000 you had unauthorized forbidden not found all of these usual errors that you encounter so many times does it make
00:26:42.679 sense at every point to say outcome. failure not found then you go to the
00:26:49.080 other place outcome. failure not found for something that practically keeps repeating itself so we can have stuff
00:26:55.480 like invalid we can have something like forbiding stuff like this right and
00:27:03.000 within our outcome now we can also have a forbidden method maybe so that
00:27:11.640 here where we have remember that in our failure this is not knable so what if we
00:27:20.399 made our error message nailable because maybe we could have a default based on whatever it was that we chose so um
00:27:28.200 error message here is new and error message here is new but we need a type so let's give it
00:27:37.320 a type of error type h not supported something like that so
00:27:46.399 do all are test still pass yes all are test still pass but let's come to launch
00:27:52.080 and now let's say you were trying to access a card that it's not yours and the external service told us oh this
00:27:58.840 blah blah blah is a scammer something something something happened so we can just say outcome.
00:28:05.919 forbidden does that work that's all here maybe we don't want
00:28:11.760 to say invalid meal we just want to say invalid param or something so let's add
00:28:18.440 an invalid param method here yes same same and let's come back
00:28:24.159 here how many minutes do I have left
00:28:30.519 amazing so maybe we don't even have an error message because now we have something default somewhere that we are
00:28:36.880 going to um indicate and now in our test we can
00:28:43.240 even be more explicit and our tests are even more readable so we can have something like this now that we know is
00:28:50.240 a failure expect the subject do error type dot to equal
00:28:58.159 let's see error type forbidding yes let's see what
00:29:05.679 happens that shouldn't work because we would get we should get
00:29:11.519 outcome yes let's run this again yeah still faced because we don't have error
00:29:17.120 type for an outcome so let's add that and we are
00:29:24.080 done what will the error type be it would be the object type right but
00:29:31.640 that's only if it's if it's a failure because if it's a success then there's no error type so let's not forget that
00:29:37.840 let's return new unless it's a failure
00:29:43.279 and then let's do object. type so let's go again Tada everything works so in 20
00:29:50.440 minutes we've been able to go from all of what we had before to this now this
00:29:57.320 is not the the end this is just an introduction to help us see how monad work Roop works in the fact that it
00:30:03.760 helps us encapsulate and chain and imagine that you already have this abstraction you could use it everywhere
00:30:09.000 in your code base where all your functions return outcomes and you can easily chain them wherever you can
00:30:15.360 change several processes together and you don't need to bother about where it failed because you would know exactly where it failed you don't need to say if
00:30:21.600 it fails here then return this if this happen if this happens here return this so now let
00:30:29.360 was look at two other things that felt like magic single track functions and the end
00:30:36.960 user those are two things that I haven't talked about and we should talk about what are single track
00:30:43.760 functions a single TR function generally does not generate any errors so
00:30:49.279 everything that we have looked at actually tells us failure or success but what if I just wanted to do something
00:30:54.679 with the code So like um available meals or add promo so you want to buy pizza
00:31:00.960 add promo add one chicken that's not going to be a success or a failure right it's just going to do something so
00:31:07.799 there's no if this happens then do this so how do we deal with this in this case as I said maybe you validate the user
00:31:15.039 it's a failure a success you get to add promo it's just a success and then you have to move to failure or success how
00:31:20.799 do we bind all of this together since this doesn't return an outcome because
00:31:26.559 it can it's it's not something something that fails we can do this via a map can you see that we can create a map
00:31:33.000 function for our outcome and if it's a failure we just return an outcome
00:31:40.039 type so that you can continue to bind it with other functions else we yield the value and at the end we send a
00:31:47.799 success as you can see I tried to do map with Pisa and add a piece of chicken and you can see what we have here we still
00:31:54.360 get the outcome the value of pizza and one chicken but when it fails nothing happens just
00:32:00.919 fails and the code we just looked at this will look something like this address is valid you bind meal is
00:32:06.399 available you map and you add a promo to that meal and then you continue your
00:32:11.600 binding does this make sense so that's for single track function so see single track functions as a combination of bind
00:32:18.639 and success which is a map now the end user does not understand
00:32:24.519 outcome types so imagine now all of this we're saying is code base right this is how we write code we have chosen to
00:32:31.039 handle errors this way you're the end user you just want to call an endpoint and get Jason right
00:32:41.960 imagine I send you outcome type success value error type you're like huh what's
00:32:47.159 happening here so how do we deal with the end user you can unwrap the Box remember that I said monads are like
00:32:54.399 containers or boxes that contain results so let's unwrap the Box for the user first step process we can have an
00:33:01.320 unwrapped error and it's going to be a standard error so this way we just
00:33:07.200 initialize it with a failure right because the only way we get here is if there's a failure and
00:33:13.039 then this is what we can have an unwrap method that returns to the user the
00:33:18.519 value if it's a success but if it's not it will raise the unwrap error so your
00:33:24.320 controller could look something like this order launch. new param and you order your results you unwrap it and you
00:33:31.000 send Jason to the user but what happens if this fails who can tell
00:33:37.559 me from this code here what happens if when you call your own wrap it was
00:33:43.200 actually a failure what happens
00:33:49.600 what yes but so that means the user wouldn't get anything right but we need to find a way to tell the user this that
00:33:56.559 there was an error so we can do this rescue from unwrap error in your application controller and now you can
00:34:02.080 do whatever you want your messages status code by type so if it's forbidden send 403 if it's not found send 404 you
00:34:08.720 can have all of these stated and imagine that you just had these four things and everything we have done here everything
00:34:14.639 is set for the entire or the entirety of your code base all you need to do is
00:34:21.800 unwrap in your controller and it's pretty easy to understand you can name
00:34:27.399 it whatever whatever you want not necessarily on wrap but you get the picture so what do we get at the end of
00:34:34.119 the day from all of these things who I made it we have composable and more readable code even our tests are more
00:34:41.520 readable you can just say was this a failure yes what type of error did I expect I expected forbidden I got
00:34:48.200 forbidden that's all you move on so imagine that I were new to your code
00:34:54.000 base it would be easy for me to just read your code and understand exactly what's happening here oh they validate the user and then they get the payment
00:35:01.599 but if the user's name is Peter we add promo blah blah blah it's just easy to read and the test you can just say oh
00:35:08.760 forbidden here we don't need to start looking at each and every single error message efficient error handling
00:35:15.599 consistency every method returns an outcome and you can chain it for as long as you like and you don't have to be
00:35:21.400 bothered because you know once it's an error it returns an error and you don't need to bother About That explicit cont
00:35:27.880 flow and reduced boiler plates some people would not agree because of the binds but we have dry monards that have
00:35:35.760 implemented a dual notation and you can use yield I personally don't like it you
00:35:43.000 I should have created like code code Snippets for that you have in our case you would have yield validate user yield
00:35:51.839 um blah blah blah yield yield yield I find it you but that's just me I prefer
00:35:58.200 I feel it's readable when you say this do bind I think I I can read it and even
00:36:03.800 a baby can see it and say Okay so this binds to this and binds to this but it might just be me as he you know old
00:36:10.319 people don't like to change maybe that's the case here but at the end of the day what do we have a happy code base a
00:36:18.480 happy user happy Ruby developers Happy Everything can you think of anything
00:36:24.000 that could go wrong except life coding of course but but any questions yeah that's
00:36:34.920 it so yeah um that's the link that's a QR code
00:36:41.760 to the link to the GitHub repo I pushed all of this so you have the main branch which has what we started with and you
00:36:47.240 have the use outcome branch which has everything it even has unwrap and map and every other thing I talked about
00:36:52.640 that we couldn't code yeah and you can find me in any of these places
00:36:58.400 questions questions questions 5 minutes I didn't know I would make it been
00:37:05.400 great one question over there please um can I have the
00:37:13.920 microphone thanks thank you bring it over
00:37:23.960 there there you go um what's the the advantage of
00:37:31.680 monads um and I don't have the word like why
00:37:38.640 shouldn't we just throw an exception raise an exception when something fails
00:37:43.960 and it goes basically automatically in a the failure track which stops the
00:37:49.760 execution and at the end of the process we can just do rescue and uh the type of
00:37:55.800 the exception is like the failure type and we avoid all the
00:38:02.200 boxing and unboxing and okay so we are actually
00:38:07.440 raising an error via the unwrap error if you remember we're actually raising it
00:38:13.240 but we are not raising it within the code we're raising it in one place which
00:38:18.359 is in the outcome with what you've said at every point where something fails you raise an error at every point where
00:38:24.680 something fails you raise an error with a monad you don't to do that you just leave everything in the box and at the
00:38:31.280 end of the day if it fails at any point we're raising the error once in the outcome so it's practically almost the
00:38:38.480 same thing but we do it in one place yeah but like raising errors is not a
00:38:43.640 bad thing like it says this thing failed for this reason and it also gives like a
00:38:50.119 stock trace for example with monads we we say okay this is a failure it's
00:38:56.040 unauthorized error but if we have three functions that can fail for that reason
00:39:02.599 like we don't know which one failed you know exactly where it failed with a monard you can tell exactly where it
00:39:08.560 failed because we have the bind so let's say in our case we have validate user. bind this do bind if it fails in the
00:39:15.440 first point that's where the error is raised from because it doesn't bind to
00:39:21.079 the next so it doesn't continue the process so we know exactly where it fails and you can't have three three
00:39:27.960 points of Errors failing you would have just it fail at one point because remember that the success of a process
00:39:35.079 is the success of the individual function so once one fails that's where it stops and that's where it fails and
00:39:41.560 that's the error that we receive so you know exactly where it feels you can't have three of them somebody else wants
00:39:47.119 to say
00:39:53.000 something so so oh sorry an alternate response is that when you start uh
00:39:58.440 working with errors right you maybe want to name them so you start putting names for the errors to know where they are so
00:40:05.040 if you have a huge hot base then you have a a wide array of names for errors
00:40:11.240 right it seems simple in this simple example but let's say you have I don't know 200 uh modules so each one has
00:40:18.040 errors and then the error has to have a name and you can there it works but then it creates a wide space for except ions
00:40:28.119 they might have message you might add some other interface like I don't know a code or things to it right so is not
00:40:34.280 standardized or you can go the other way to say I will raise a one single error and I will put a payload of code and
00:40:40.640 that is similar to going to this direction organizing errors in some way so I think modad is is more like you
00:40:47.640 know that wherever you are the the failure looks like this and it has a payload and you can raise an exception
00:40:54.400 with it if you want but it's a common interface in a way that this is how I see it I don't yeah also if you if it's
00:41:01.240 very important for you to raise an exception let's say there are points where you really need to raise an exception here you can remember that we
00:41:09.400 have a failure type and we have error message we have type you can also have exception there as a key and add the
00:41:17.280 exception and in your raise on wrap let me see here where we rescue here if there's
00:41:26.200 an exception you can raise that exception there does that make sense in
00:41:31.920 some way mass or menus also what you have said is practically almost the same thing like
00:41:38.880 what we do in monads it's just that we do it in one place so let's say you raise at one point we are already
00:41:44.920 raising the error in one place and we will still raise it's not like we would be silent to errors no but it's
00:41:51.680 everything is now composable do you understand because let's say it was successful you cannot compose it to the
00:41:58.720 next method but if we wrap everything in a monard we can actually compose methods
00:42:03.960 irrespective of whether they are successful or a failure any more questions
Explore all talks recorded at rubyday 2024
+1