 So welcome everybody it's good to see you all here and we're gonna talk about confident elixir today I'm hoping my voice holds out if we end up with sock puppets and interpretive dance by the end of this that'll mean it didn't quite work out but hopefully I got a half hour left in me so talk is confident elixir the inspiration for this talk is a book by Avdi Grimm called confident Ruby how many people here have a Ruby background yeah you read the book wow there's a lot of hands out there so yeah I found a lot of value in this book and I started thinking about so how do some of these ideas translate into elixir and I think they translate quite well actually elixir is a very natural fit for some of these ideas so a big portion of the book is is about patterns but it's not a patterns in the large it's not like a gang of four style pattern where it's at the class level or multi-class interaction level this is more implementation patterns so it's patterns sort of at the the method or function level so we're gonna we're gonna keep to that scale throughout this talk so natural question what makes code confident and of these answer is confident code tells a clear and coherent story and of course we prefer short stories to novels in terms of code especially at the at the module at the model level so how do we tell a good story and a good story has four parts and the order matters so we want to be able to read a method or a function from top to bottom and we should see these things in order so first we're gonna go gather data we're gonna do some work we're gonna return values and we're gonna handle errors alright and then there's some other things along the way so we want to have very short functions that reveal their intention very clearly we want to have minimal use of conditionals and we want to have minimal use of error handling for flow control so I'll be in the book he brings up this idea of these choose your own adventure books remember those so you get to the end of the chapter and it says something like you know if you want to see the princess kill the dragon go to page 250 otherwise go to page 300 and those books are great if you read them in that order but if you start at the beginning and you read from front to back it's chaos right because princess has already killed the dragon and then all of a sudden the dragon is back because you've you've moved them out of order so conditionals can tend to do this in our code and if you take that to an extreme you get something like this which had tipped to Greg Vaughn this is the gilded rose which Greg has kindly translated into elixir and you see I this this doesn't tell a story this is this is really hard to read really hard to figure so what we want to do when we're writing coffee code is avoid stuff like this I wish I could say that I've never actually seen code that is this extreme but I actually have I haven't written it but I've seen it all right so if my high school English teacher is watching this is this thesis for the talk elixir is built in language constructs make writing confident code easy and natural and I actually really believe that so let's take a look at some of the building blocks that elixir gives us we have pattern matching very powerful we have multi-class functions also very powerful guard clauses these three work together to allow us to tell a really clear and coherent story to break down conditionals and we also have supervision trees which are like the ultimate in in writing confident code so let's go through the building blocks quickly the first is pattern matching so if you've written any elixir at all you've seen this on the left we have the pattern in the middle we have the match operator on the right we have the value so the equal sign is definitely not an assignment operator it's a match operator and its job is to make the value and the pattern match so if on the left we have a variable that's easy all the match operator has to do is bind that variable to the value on the right and away we go so let's take a look at some examples that's the one that we just had there will notice also that the match operator will return the value at the end that's the return value so we can turn it around and because we've already bound the value a to the value seven this just says seven matches seven perfectly great right we can rebind in the new match some purists have difficulty with this but never fear all as well the values are immutable they never change just a different binding so this returns nine we can also use the pin operator which says don't rebind use the last value for the variable that you were bound to this will fail because we just bound it to nine so it's saying nine matches seven that's not going to work there's also the underscore which matches anything but does not bind to anything so this will match but if we try to use the underscore again we get a compiler because it's unbound so we can use all of these things together in pattern matching we can get a little bit more complicated so we can use literal values for a pattern so we have three element lists three variables matching a value of a three element list of literals the return value is obviously the list but what we get for this is we get destructuring for free right so all the variables are then bound to the values on the other side this pretty standard stuff so the number of elements matters and also the types matters you can't have any mismatch there otherwise it's going to blow up so there's one last thing about lists so we can actually pattern match to pull the head off the list and also pull remove it from the rest of the list so the head is going to be a single value and the rest is going to be always a list even if it's an empty list tuples the same same thing here but once we get down to literal values in the pattern this last one is what's called a tag tuple so the first element gives us more information about what this tuples all about and we can pattern match on those as well and we still get the data destructuring that we care about okay maps are more interesting a little bit so an empty map will always match another map always but we can also add keys now keys have to match but we also get data destructuring with this as well so our name comes off and it's bound to Frank if we actually give a key with a literal value that doesn't match we get a match error and as we would expect if we give it a key that doesn't exist in the map that will be a match error as well structs maps will match structs this will always match right but the reverse is not always true so if we have a struct even though these both represent the same kind of thing structs have extra keys and values in them to say what the module they came from is right so the map unless the map has those in there which is unlikely that's not going to match okay and pattern matching in the wild where do we see these things in assertions that's what we've seen so far is these these things used in assertions but they're also in function heads so we can we can pattern match on the arguments that come into a function and unless the argument that comes into this function is a user struct with a name key this function will not be called and we can use function heads with extra binding so they're that equals user that the user variable will be bound to the full user struct coming in and will also have the name variable being bound so inside the body of this function will have available to us the name and the full struct okay and in case statements so here in this line that's the value in pattern match against the patterns here okay user or error reason and we'll do the right thing along the way alright now multi-clause functions this is the second building block that we have so we can define multiple clauses of a function with the same name which is weird if we're coming from someplace like Ruby and these are all clauses of the same function or we can define multiple functions with the same name but different arity and those are different functions so let's give some examples of that these will all be same function and we notice we when we name functions we name them by their name and arity so this function is say one right so we get a single argument per se and we're using literal values in here right so that they will pattern match to differentiate themselves obviously order matters so if we had switched the order of these and say we have def say words on the top that will always match and there's no chance that the other two would possibly even match so the compiler will give us a warning about that so we want to put the one that will is sort of like the fall through the de facto put that on the bottom okay and these are different functions even though that's a list it's a single argument and this is a double argument so that's another one and another two okay let's see here so this is a legitimate question are these things better than branching conditionals multi-class functions I think they are because each clause is much more focused tells a much shorter clearer story and using pattern matching and what we're going to talk about next guard clauses we can be very very specific about the code that goes in each one of those things with the condition we can be specific about the conditions for each one of those and all of the behavior for a given set of specific conditions is really separated out from all the other things and now on to guard clauses part three so guard clauses are just functions right and their job is to check arguments at runtime just make sure we're getting the right thing and they work together again with multi-class functions and pattern matching for really really super fine grain control there are a limited number of these things that we can use because they have to be super fast so imagine if guard clause had to go to the database and get a value and bring it back to make a determination whether this function should be called or not it would kill performance that would be super terrible so here are some of the available functions that we have these type check functions is Adam is binary there's a whole bunch of these so I only listed a few there's some comparison operators all our old friends there's some arithmetic operators excuse me so we can do math when we're doing guard clause and Booleans it's a whole list of all the things that we can use in guard clauses available and they're getting started guide all right let's take a look at some examples so we the the when keyword here is used to begin a guard clause and now we're checking that this word is a list and remember single quoted string is actually represented as a list of characters not like a double quoted string is a UTFE encoded binary so in some cases we need to have a list so if we need to have a list this is the thing that will work we can do comparisons obviously doesn't make sense to compute a square with exit is less than zero we can mix and match these things with Booleans and we can mix and match all of them if we want to in the final building block that we have is supervision trees and these really these make it super possible to do really really confident code and I say think unobtrusive JavaScript for area handling anybody is unobtrusive JavaScript still a word does anybody know what that means yeah so so the idea behind unobtrusive JavaScript is back in the day we used to write a lot of JavaScript but we used to write flat HTML files put a bunch of JavaScript for that page in the head tag and then do the bindings the event bindings in the markup itself and then we decided let's take all of that out put it in its own file and then you CSS selectors in order to figure out which element we want bind the behavior that we want supervision trees do that with error handling code so right now in most languages we mix our business logic and error handling code together and with supervision trees we just take all of that out and put it off in its own place so it can do its job the business logic can do its job and we'll talk more about that when we get to error handling so let's focus on what it is to tell a good story so the four parts remember this is the first part of telling a good story collecting input in order to be confident about this we got to make sure that our types are right right and most mostly what we end up doing is we end up converting our types and the easiest way to do that is to do conversion and the best place to do it is at the borders of our code so since we're talking mostly about functions and methods here the border would actually be the the function definition so in Ruby we might do something like this notice is that 2i just to make sure that the iterations is an integer but in order to do this explicit conversion we have to do this in the body of our code in the body of our method so we're already sort of breached the border already and in elixir we might do something like this now we do a check at the boundary first to see is this thing a binary if it's a binary we convert it to a character list and we call ourselves recursively then we have another clause for this when we call ourselves recursively the the virtual machine is going to go back to the top and it's going to run through this first clause again not going to match going to fall through to the second one and then we're going to call our airline function this actually is something that happens all the time because airline expects strings to be character lists and elixir wants them to be utf-8 encoded binaries so if we're doing airline interop this is this thing that happens fairly often so the question is should we go hog wild with this which we like create a clause for integers and booleans the answer is no I think because we want to just code for the common case and for anything else we just let it crash so we can also do custom conversions and this is something code snippet from Phoenix itself where protocols give us the ability to create implementations for each type that we might want to convert right and so we can confidently just say to param on virtually any type that will come in and there'll be some way to handle it okay on to doing work okay doing work is all about sending messages and calling functions right and the main thing is we need to trust the receiver to do the right thing we just want to make the call right we don't want to ask the receiver do you respond to this message are you such and such a type we just want it to go just want to call it and go so if we let's not do this but if we do have to query the receiver we end up creating conditionals right so if it depends if the message we're going to send depends on something about the receiver we're going to end up with problems like this and this can grow I've seen this a million times so let's do something like this instead this is a classic duck typing polymorphism so we make sure that no matter what gets passed in as a rate sheet that it will always respond to the calculate method and go ahead this so this is a ruby example and let's give elixir its turn so what about this so at first of all we do check at the at the boundary at the at the function definition itself its boundary is this input okay for us if it is we just start sending calling functions and we don't ask at all about what this thing we don't ask at all whether the receiver of these functions can can handle it or not and so the pipe operator just exudes confidence in that way let's take an active record example this is slightly different so let's say we just had a wrapper function a wrapper method I'm sorry around something in active record since save returns a boolean boolean doesn't tell us all that much about what's going on we have to ask it so okay we did it work did it not work and then take action accordingly but in ecto ecto the insert returns a tag tuple so we can just take this the return value this and just pipe it into a handler function so if it matches okay model this tag tuple we do insert things otherwise if it matches the error chain set we do error things and we're able to split out the the functionality for each of these cases again the question so is this is really saving something well if there's only one or two lines in each one of these maybe not but if these things become complicated like we have to send emails and do a variety of other things the cognitive load of these things all in one function can become large this makes it a lot easier to think about and debug later on and so the third part of telling a story a clear story is return values and three things we want to do with those want to make them benign basically not nil make them meaningful like give them some extra meaning by adding information to their surface so think about the last example they active record return value is just true or false doesn't really help us out that much but with ecto we get a tag tuple the tag actually gives us a lot of information did this thing work or not actually ecto used to return just a change set and we would have to query it for errors was the thing valid or not and then make decisions based on that no longer have to do that we can just send the message on and go so benign values so thinking about think about the case where we have three possibilities none or three possible return values none one or many this is the right way to do it we give it either an empty array a single element array or a two element array or list instead of this and why because the second way we have to ask are you nil your string are you an array are you a list we have to keep asking questions and as we ask those questions we build up conditionals okay the other way to make a benign value is to stop substitute a null object so the very common case is in web application we can either have guest users or logged in users well we can either have a logged in user or nobody right and so oftentimes if we just return nil if we need to fill out the UI we have a real problem because we're asking nil what's your name we're asking nil when's the last time you were here so we create a null object that has the same API as a regular user but has canned data in it these are great but they're also a problem because we need to make sure that these APIs don't shift thing is in elixir it's all just data right so a user is represented as a struct guest user is just a different struct with some canned data in it if you want to get fancy we can put that behind guest user function and it returns the the pre-canned data that we actually need so we also have meaningful values we need to have more meaningful values so in case where we might return nil or a value we can return a symbol instead or an atom atoms are easy to pattern match against so here's an example so if we want to register a process in our in our virtual machine this will return an atom yes for success or no for failure it's not nil nil actually doesn't tell us anything it's like is there something there is it not something there did it work did it not work it's just nil we and we can't ask it any more questions because it'll blow up and we can have even more meaningful values these are tag tuples which we've talked about and so we've seen the the okay model error reason a bunch of times already it makes things it makes pattern matching really really simple and easy and we get the added benefit of being able to destructure the data automatically get that for free and we don't have to query the thing to figure out what we need to do next says all kinds of examples season this pattern is everywhere so if we start up a gen server we get a tag tuple with okay and a pit actually if we open up a file we get okay and a pit as well Phoenix channels return tag tuples all over the place so for join three get either okay socket or reply for handle in we get all of these and so on and innumerable actually inside for each enumeration it will represent an accumulator as a tag tuple figure out what it's supposed to be doing next so this is this is adding extra information to our response values and now on to error handling so in most languages this is where it begins and ends right you've got begin raise rescue all of these keywords and we put all of this stuff inside of our business logic but in elixir I mean let's cut to the chase we rarely ever do this in fact we should kind of just forget these things ever existed for the most part there are reasons to use it especially if we need to present error information back to the user but for control of the flow let's just not not do it instead we build a supervision tree and we just let the errors crash the process they occur in so why is that a good idea that's a legitimate question because in most languages if you've got untrapped errors all over the place you're gonna have a bad time right but for us the reason this is a good idea is that we can't actually plan for everything it's gonna go wrong and we can expend a lot of energy trying so let's just not do that anymore and we always know that there's something that's gonna fail so let's focus on recovery instead and part of the idea is that with a supervision tree something down here a leaf might die and all we have to do is restart that leaf and we localize the error to that small subsystem instead of letting the error propagate throughout our system and how can that even work again in most languages this is not even a possibility so because we have processes VM level processes that are completely independent they don't share a stack or a heap with anything else any of these processes can crash and it doesn't affect any of the other ones because processes have an identity they can be linked together and they can monitor each other and when they can monitor each other then a monitoring process can start to take action if another process is dying usually that means restarting it to a known state by one of several of these predefined strategies let's talk about those next so one of the strategies is one for one so if one worker process terminates it'll be restarted so middle one dies gets restarted away we go another one is all for one so if this middle one dies they the virtual machine will terminate all of them and restart them to a known state and this one rest for one so we have to imagine that these processes are started in an order so that the one on the left is started first middle one next and the one on the right is last and they have some dependency on each other so that let's say the one in the middle crashes we will restart it and terminate them any of the processes that were started after that and restart them like that and the final one is a simple one for one this is basically one for one with only a single worker per supervisor and the documentation tells me this is good for when you're attaching dynamically attaching workers to supervisors so that one dies that gets restarted all right so what does this all say about confidence so we keep air handling code out of our business logic so the story remains clear uncluttered and we really code for the happy path and let the chips fall where they may because the supervision trees have our back they're gonna restart things for us when things go bad that's it this is me and if you're interested in more of my work I'm the principal author and maintainer of the Phoenix guides that's mostly what I'm known for in the world