 Hello everyone. I'm going to be standing still so these guys don't get a headache with a camera and we have to play the version of musical chairs for cameras. Right. I'm here to talk about functional programming. I'll rather some of the things we can learn from functional programming when it comes to writing Ruby, specifically writing Ruby tests. Right. I'm Peter. As I said, I won't bother you with my family name because no one's going to get that right anyway. I run a small consulting shop here that does what we call managed IT services primarily for shipping and transportation companies. I've been working with, I've worked with Ruby for about 10 years and we've written quite a lot of Ruby Glue code. We also have some rails, done some rails work, but it's predominantly glue code to to get other things to talk to each other. Let me just start out by saying what this is not. Because when it comes to when it comes to functional programming, a lot of people sort of their eyes tend to glaze over. And this is not actually a talk about functional programming. This is not a weird hardcore talk about the details of the various languages. What I'm on it is or how category theory works. That's primarily because I don't know exactly how it works. So I wouldn't want to make an idiot out of myself. And we're not going to talk about how certain things are, how a method really isn't a function because people define a function as something else than what we Rubyists think that it is. It's also not about how you can't actually do anything because without any, as long as you're talking pure functions, you don't have any side effects. Without any side effects, nothing really happens. So this is really a lot more about all right. And lastly, this is not a practical talk in the sense that here's a gem you can install. Once you do that, then everything is just going to be nice and easy. This is much more of a mindset talk, so to speak. So there are some sound functional programming principles. That you can apply and it can make you think about certain things in a certain way that will help you. I guarantee whether I can bring it across as something completely different, but there are definitely principles behind it that will make sense to you. It will enable you to write better code, whether you actually do write better code. Again, it's something different as a consequence of better code. You will also be able to write code that can be more easily tested. Most people that write Ruby, they are also to a certain extent writing something functional style ish. And we're going to go through some of that today just to make it a soft intro. But like I said, this is a very, very, this is planned to be a soft and smooth intro into this. If you have any questions as we go along, please just ask. We have a fairly small crowd, so that there shouldn't be an issue. We also okay on time. If there are any mathematicians amongst you who get pissed off when I use the wrong word, please tell me afterwards. Right, what is it about functional programming that we're interested in? Well, it's actually just a few things. There are many, there are many aspects of it that are interesting. But for what we're trying to talk about here now is the fact that a pure function has no side effects. And the fact that we can write stuff in a way where we do not expect side effects will make certain things easier for us. We'll get to it. We don't like global state at all. In general, people should try and refrain from dealing with global state. And we don't like mutable state either because it gives us problems. It gives us headaches. So this is basically what we want to try and take away from this. Is there a way we can go about writing regular Ruby code? Again, with regards to tests where we can avoid this. So are we writing today? Well, I mean, what are we writing? That's that's FP ish when we when we write Ruby. Well, this is admitted a little bit small, but this is kind of the shortest pure function I could come up with. And I know it's it's an instance method on an object. And therefore it really isn't but never mind that. But here we have a function called add that takes two parameters X and Y and we add them up. Right. There are no there are no side effects. This is all we we pass it in some stuff. We get some stuff back out. And that's really what we should be trying to to move towards. Right. Here is all right. I really need to see if I can I don't know if I can scale this. No. Yeah. Can you see? I know it's a little bit small. Oh, it's up there as well. Well, that's cool. It's still really small, though. But anyway, you guys, you're going to have to bear with me. So what we do here is right. We just take an we have an array and we just assign some some values to it. It's really quite straightforward. We could write a for loop like we would do it in C as an example. Nobody really writes for loops like this in Ruby, but you could. That's really not much preventing you from doing it. You cycle through it. You could also write this in a sort of a slightly more Ruby-ish way. Again, you probably wouldn't, but you could. And then there's the sort of the more common Ruby way again, where we just we we use the each function and we pass in a block and then we print out the contents this way or, of course, the better version, which is where we call join on the array. We tell it how to join and we put that out afterwards. Right. So this is actually if you're sort of really being being strict about this, then we have the traditional imperative way of doing it. We have that up here and we have something that's almost really functional program-ish down here and even to a certain extent down here as well. So so we're actually doing it. We are already doing is not really something to be too concerned about. It's it's everybody knows this stuff. Everybody has written some variation of this before. If we are doing something with a file, again, we open a file, we pass in a block, we do some some some stuff with this. And this is again in within the paradigm of functional programming. We can do some stuff with arrays again, where we say, well, we want to double we want to double the output. We we as in double we want to multiply each item in here with by two. We could do that by having a defying an array outside and then loop through them and then multiplying them. We're probably not going to do that. We could also call a map function on it instead. And because this is Ruby, we can we can chain functions. So we could even if we really don't care about the variable called doubles, but we just care about the contents of it. We could even do something where we then again pass in run each and the output of that of that array that gets created by the map function. And then we do something with that. So again, this is this is very standard and very straightforward. Ruby, everybody would have written that in some shape or form. But again, it is functional programming ish, right. There are some problems with this. And that is that we have to think slightly differently about it. If you if you come from a you can always if you're looking at Ruby code that's written by someone who has a background in something else, you can always you can always see it straight away. It just it just looks wrong. I mean, it's syntactically correct, but it just it just looks wrong. And again, we have to think about certain things in a different way. And that that that is problematic for for for some people, or at least until you get your head around it. But the one of the problems we have is something like mutable state because mutable state is very nice for certain things. It allows us to do something in a certain way. So here's a fairly common way of we have a class called widget, we have an instance variable called options. And let's say we want to we want to create some tags for this particular thing. And we want to be able to do that based on some options. So we have a tax function, we define a variable. Again, we set that to an array with some some contents. And then we look inside and in this instance variable options if a particular if a particular flag has been set. And if if it has, we add the not cool tag to the existing list of tags. This is that's actually does nothing wrong with this. This is a this is a perfectly fine way to do this. We are mutating we are mutating state because we still have that same variable that we are touching, which we technically shouldn't do. But the problem is that if we were to write this. Well, let's say we wanted to make a few additions to this. So we have now here version two of this. So it now is no longer just not cool. We also have a few other tags that we want. We may want to be adding based on these options. So now we loop through this list of this list of. Excuse me, this list of symbols and if the corresponding option has been set. We add that to our list of tags, which we then return down in the end. But you see, if we were to write this in a technically more functional programming way. We end up with something that's really not particularly nice. Because one way to write it. I'm not saying there's not nicer ways to write this. I'm just saying this is one way to do it is where we define all the different options. And then we run reject on that. And we say the ones that we don't want, we get rid of those. And what is left behind of this is now the tags that we were dealing with before. And this is, in my opinion, this is not particularly pretty. But if you really wanted to be sort of a purist, then this would be the more accurate way of going about it. If you look at these two side by side, I mean, if you make changes to your code. One thing is your ability to read it or your colleagues ability to read it. I mean, I personally think that the other version where we are actually mutating. I think that's an easier way to do it. And if I was writing this, I would probably write something along those lines. I probably would make it a little bit more functional in the sense that instead of having to deal with an instance variable up here, I would personally prefer to simply pass in the options as a parameter to this particular function. At least you're not sort of putting your groppy hands into different places where they don't belong. Everything that you deal with here gets passed into the function. Now, sometimes ignoring these principles means better code. It's a matter of preference, but it's not a black and white thing. But what is it that we want to achieve? We want to have no side effects. And someone was talking about rails before. And if there's one thing rails does well, man, it's side effects. We have weird callbacks. We have odd validation functions. We have stuff that can be a real pain in the ass to troubleshoot and to debug afterwards. But we would ideally want to have no side effects. We would ideally want to have no mutable state if we can. And we would ideally want to have no global state. So let's have a quick look at testing. Now, there are many types of testing, of course. Unit tests are very easy to write. They're very easy to understand. They're easy to get your head around. They're easy to change further down the road. You have smoke tests where you really say, I just want to be kind of sure that I'm sort of getting the right thing, but I don't really care too much about the details. Those things can be very useful as well. And then you have integration tests, which I'm guessing most people hate, because you very easily end up in a situation where you create, first of all, they're hard to write. Secondly, you end up creating some dependencies between the different modules, and it very often ends up with you testing implementation, the actual implementation as opposed to what you were really trying to achieve. So maybe... Oh, right. When we are testing in an object-oriented language, we are also often testing the object. And the object, as we know, because an object is an encapsulation of data and functionality, is just global state on a smaller level. And many people have a tendency to write these massive class definitions where a lot of stuff goes on, which probably shouldn't be there. So you're saying, well, my functionality is constrained to my object. Yeah, but it is really kind of global anyway. Testing in Rails is also quite interesting. The whole DHS said fat model skinny controllers, and people thought fat meant morbidly obese. I'm personally not a huge subscriber to this fat model skinny controllers to begin with, because I think there's way too much functionality that ends up being loaded into the models, but that's a completely different discussion. But testing in Rails... Oh, yeah. It's not nice. There are simply too many moving parts. There are too many bits that are interconnected that can make it extremely difficult to make sure that you are actually testing what you want to test and not just some odd side effect of something else. So what does that mean? That means, well, if we can do more unit tests and more smoke tests and less integration tests, it should be easier for us in principle to write more maintainable code. It should. Our words of words is should. Now, I am going to show you a few examples. They're very small and they're very self-contained and they're very, very wrong. And it's actually from a live code base where just a few things have been changed to protect the innocent. But take a look at this test as an example. This is a very basic aspect test or at least part of a bigger aspect test. So we're using something like, I think it's Factory Girl that was used for this, but it doesn't really matter. You're using a Factory Helper tool to create the object that you are testing. And now we want to test here that in case the default color is empty, that it gets set to something. And I'm actually quite amazed. And by the way, I wrote this. So I can't blame anyone else. I actually wrote this. It's quite old, but that doesn't matter. It's still there. We have a test here with three lines in it, and they are all wrong. That's a three out of three. That's pretty impressive, right? But it's actually, this is really a poster boy for how you should not do it. First of all, we have our subject that gets created outside using Factory Girl. And then we start fiddling with it. We start setting the color to nil because that's what we want to test here. Then in order for us to actually trigger the logic that deals with this color being nil and setting a default value, that gets done as a, I believe, as part of a before save hook and rails. So we have a, we have side effect of us actually saving something. There's data that gets assigned. And then finally, at the very end of it, where we do our, where we test our expectation, we are now looking for an implementation detail because we are setting, we are saying specifically that we want that color to be the symbol red, which really doesn't make any sense. That is just an implementation detail. It doesn't matter. What functional programming teaches us about not sharing state, not having global state, and not having any side effects, is something that sort of gets us towards this instead. We create the object here that we want to be testing against. And as part of the creation, we make sure that the color is set to nil. We don't try and change it after the fact. Ignore subject save for now. And then finally, we obviously test here. We're just testing that the color now is now no longer nil. Because again, the red was an implementation detail. We don't care what it said to. We just care that it gets set to something. And that's really what we should be testing. That's what we should be testing for. Any questions so far? Or you just blown away about how interesting tests are. Yay! No? No, yay. Okay, fair enough. Like I said, this is also an exquisite example of how to write an impure function. Because we have a whole bunch of... What happened here? We have all this magic that happens inside these before and after hooks that Rails is so famous for. We have validations that run, or maybe run, depending on how the validations are defined. We have this peculiar chain of events that can get triggered and maybe get aborted halfway through if something fails. So there are better ways to deal with this. But we're not... If you go for... For those of you who are coming from red.ru, I will strongly recommend you are there for Nick's talk on Trailblazer where he covers a whole bunch of this stuff, which is a really phenomenal source that solves this specific problem. But in general, the way to approach this, the way I see it is, well, any time you're dealing with any data that gets sent back and forth, every time it gets processed, the rule is that you should just ask yourself, can I make this a pure function? Can I provide everything I need for this particular function without having any side effects? If you do that, it will be easier for you to write. It will be easier for you to maintain. It will be easier for you to test. With OOP, there is shared state. There really is no way around that. But it doesn't mean it has to be global. Try to keep it relatively local. Future you will thank present you for that. And there are some more questions you can ask. Can I make this smaller? Do we have all arguments provided? Can I avoid state at all? And are there any side effects that can be avoided for this? And this is not rocket science. In all fairness, this isn't really about functional programming either. It is really just about sound engineering practices. But it's one of the things that at least we can learn if we try to approach this from a functional mindset. If you want to check some of this stuff out, like I said, the Trailblazer talk at red.ru will be awesome. If you haven't read the book, I can also recommend you get the book. I'm not getting a commission, but I should. Mingding also mentioned DryRB. And it's phenomenal what those guys are doing in there. It's a different way of thinking about how you make your applications do the things that you want them to do. DryRB is not just based on... It's not just related to web applications. Trailblazer very much is. But I would strongly recommend if you want to sort of dip your toes into this, this is where you should start in my humble opinion. If you have any questions, you are very welcome to get in touch. I don't do social media, so we can't do that. But, why are you laughing? But there are these other ways that you can reach out. You are very welcome to, and I'm also going to be here a little bit longer today. So if you have any questions, feel free to come and ask. Like I said, if you have a math background, let's take a rain check on that. Any questions? I'm sorry, what? No? Alright. Thank you very much.