 I'm an engineer in Netflix, and I work on applications that directly help produce Netflix originals, like stranger things, and to get down everything from scheduling to casting to managing script pitches. We don't use Elixir internally yet, but we're very interested in exploring it. So come speak to me after my talk if you're interested in what we're up to. You can usually find me on their interwebs as Sugar Pirate. You can reach me on Twitter at Sugar Pirate if you're interested in it. Or on GitHub as Potato with an E. As you probably can tell, I can spell, and I also like food. So I find starting off a talk with a picture of my dog to be really helpful. And this is probably the best slide in my talk. It's all downhill from here. And here she is in the office with my poop pillow. All right, so let's get on with the real talk. So I guess I should start from the beginning. My first experience of the web was in high school. My dad gave me a pirated copy of Photoshop, and I spent many hours of my childhood making avant-garde lens flare art, like real cutting edge stuff at the time. Some of you might recognize this photo from the new Star Trek movie. As a side note, this is one of my favorite quotes from that movie, which is also why I started off this section by saying long time ago in a galaxy far, far away, which is a great Star Trek quote. So anyway, I learned a lot about design in the web by tinkering around with it. And I learned mainly from tutorials and forums that I came across online. You know, the very first time you right click on a web page and you view the source, you feel like such a hacker. It's great, right? And in hindsight, without pirated software, I'm not sure if I would have ended up in this career and learned as much as I did on my own. And even though I couldn't afford the real thing back then, software licenses and subscriptions are pretty affordable now, and sometimes even free, which I think is really great for lowering the barrier into technology. I also learned HTML and CSS from my friend in high school. The first thing I learned was how to use the marquee tag, which in my defense was kind of cool back then. Anyway, fast forward many more years. I went to college. I founded a startup, and I started doing front end work more seriously. I started learning JavaScript and especially enjoyed the functional side of things. For example, being able to use a map filter and reduce is really nice. In JavaScript, these all return new arrays, so you don't mutate the original. And JavaScript is actually a pretty nice language to learn functional programming. For example, functions are first class, so you can write higher order functions pretty easily. And you can also make use of lambdas and closures, which are really nice. And I also explored libraries like Ramda, which allow for more functional approaches in JavaScript. So all in all, I would say I had some experience with functional programming prior to Elixir. And honestly, the best way to get started with functional programming is to be comfortable with functional programming basics versus maybe in a language that you're already familiar with. And JavaScript isn't such a bad language, because it's used in spacesuits, so it can't be that bad. And after founding my startup and not really getting anywhere a bit, I got my first job at another startup in Australia. The app was built with Rails and Ember in front end. It was pretty difficult learning Ember at the time. But Ember has improved a lot since then. And actually, Ember and Rails were my first instructions to MVC. And I learned many new concepts working in Ember, many of which translated well into server-side development. And after working on a Phoenix API for my last job at Dockyard, I realized I could also bring some ideas from Elixir into the front end. For example, I wrote an add-on for Ember called EmberChainSet, which, as its name suggests, brings change sets to Ember. So it lets you work with client-side models in a really nice way, complete with validations and rollbacks. And it's been such a great experience learning Elixir. And I realized that both the front end and back end can inspire each other. And aside from Ember, of course React and Elm are also great introductions to functional programming in the front end, especially when you pair it with, for example, Redux for state management. So with that, let's move on to the main part of the talk. And hopefully you'll be able to walk away feeling inspired to cross-pollinate ideas from Elixir into your front end and vice versa. So let's look at what we're going to discuss today. First, we're going to talk about Elixir. It's important to start with the basics. Then we'll look at how to understand Phoenix and write apps the Elixir way. And finally, we'll discuss the temptations of bringing OOP into Elixir. So first, Elixir. Before you even dive into learning anything like Phoenix, you should obviously have a strong grasp on Elixir basics. So I'm going to cover the things I think are important to know and explain them as simply as I can. And understanding Elixir's philosophy will make learning it much easier. So Elixir is a wonderful language. That's why we're all here today. And every time I use it, I wish I could write it everywhere. But the more I use Elixir, the more I realize it's not just sugar on top of Erlang. So a lot of the power of Elixir reveals itself when you learn how to use the OTP, which is the open telecom platform framework implemented in Erlang. And Elixir, there are many abstractions for solving distributed and concurrent problems, including GenServer and GenStage, which we saw today. I'm actually not going to cover any OTP concepts in this talk. I think it's a whole topic in itself. But I did mention it because it's important to remember that Phoenix or whatever web library you use is only one part of your application. And the core domain logic for solving distributed problems will make use of OTP things like tasks, supervisors, and so on. And so it's important to remember that Phoenix is not your application. It's only the component of your application that exposes it through a restful interface. And once you're familiar with Elixir Basics, I highly recommend checking out Benjamin Tan and Sasa Jurek's books. They are both really good and comprehensive reads on both Elixir and OTP. So first, let's look at how to learn Elixir. The feature that usually first comes to mind when you learn it is the pipe operator. So here's a really simple example. The pipe operator lets you write this, which is really hard to read, like this, where each pipe passes the value from the previous function to its next as its first argument. Now, it might seem like simple syntactic sugar, but it's more than that. One of the obvious benefits is that it sets you up to write smaller functions, which tend to be easier to reason about and test. But pipe isn't just sugar. It also enables a very elegant way of transforming data in a composable manner, as well as allow you to nicely handle both the happy and unhappy paths. And when you combine pipe with pattern matching, it becomes even more powerful. And you'll realize that you almost never need to use a conditional, like if statement. So when I was first learning Elixir, I think pattern matching was one of the hardest concepts for me to grasp, because I had never encountered it before. But once you get it, it's really intuitive. And if you come from a JavaScript background, it's kind of like destructuring, but with type checking and value matching. Again, with pattern matching, you almost never need to use conditionals. Instead, you can define different function heads to handle different kinds of data. Pattern matching forces you to think about your data first. What are the possible values this function will receive? And how do I want to handle them? Here's another example where we have a plug using the case statement to match some currency option that's being passed in. While this is all fine and it works, the pattern match version reads a lot better. And you can be explicit about handling specific errors or even letting it crash. Pattern matching is also a really nice way to map some input to another output. For example, you might want to respond to incoming web hooks in some controller in Phoenix. And you might want to send an email to your user based on a topic of that web hook. So mapping a topic to an email subject becomes really easy with pattern matching. And you can imagine doing this for any other kind of mapping. This is the second best slide in my talk. So another thing that you'll definitely be introduced to when you first learn Elixir is recursion. Recursion can be a really useful tool, for example, when you deal with things like tree structures, or you need to reduce values down to a single value, and so forth. So let's say we have some JSON. We want to flatten all of these nested objects into one top-level object. And let's just say that the shape of the JSON is variable, and you need to be able to handle deeply nested objects of various depths. Recursion is a great fit for this kind of problem. So let's write a simple function to flatten some JSON. Here's the entry point for our function. Let's assume that the JSON is already parsed as a map. So first, we need to convert the map to a list. And this will let us use Elixir's pattern matching on linked lists really nicely. And when you create recursive functions, it's usually helpful to start with the base case first. And the base case is where the recursion terminates, and optionally returns a value. And without a base case, you will get an infinite recursion, which is probably not what you want, unless you do, which is OK, I guess. Anyway, in this case, if the recursive function receives an empty list, we'll just return the accumulator, which at this point is the completely flattened object. But of course, we need to build that accumulator up first. So we can pattern match the linked list. And here we have the case where the value is a map, as you can see from pattern matching the head of the list, which is the first key value pair. And then we match the map literal to the letter v. And since the value of this pair is a map, we also need to traverse it as well by converting that into a list. And then we call that recursive function again. So now we need to handle the case where the key value pair is not an object. And in this case, we just want to add the key and value onto the accumulator using map put new, and then pass that along to our recursive function. And then keeps on going until we hit the base case. And then we're done with recursion and we retrieve our flattened result. Breaking it up into smaller steps helps you build your recursive function in a more straightforward way. And here's the concise version. Recursion usually does my head in. But I found it easier to do this in Elixir than in a language without pattern matching. And coming from a front end world, sometimes you deal with, like, you know, deeply nested tree structures and, like, just weird JSON. And recursion can be very useful tools in those scenarios. But if you had written those in JavaScript, it would have been full of if statements and probably really hard to debug. Now in functional programming, we like to think about transforming data. And there are many kinds of data types in Elixir, unlike JavaScript, which only has one standard dictionary implementation. So let's look at the data types in Elixir that deal with key value pairs. When data comes from the outside and you want to represent it with a dictionary, you want to use a map. For example, your API might receive a JSON string that you want to parse and use in Elixir. And when you deal with data that comes from the outside, you always want to use a string key to map. And this is because when you use Atom keys, it actually does create an Atom. And because Erlang has a default max Atom table size of about 1 million, and also that in Erlang, Atoms aren't currently garbage collected, what happens is when you create more than a million Atoms, it will basically crash Erlang and all the processes that run, which I'm not really sure if you can recover from. So while you might not create 1 million Atoms in development or by itself, if you create them dynamically from user input, then it could very well exceed that limit over time. So be careful about external data. And when you do have to create one-off maps internally, you should prefer to use Atom keys. But this might lead to your map becoming just like a real free-form bag of data, which is not always the best and makes things a little less explicit. And in those scenarios, structs are typically better since it makes things more explicit. For example, models or in ecto schema are basically structs under the hood. And in our previous example, it can help to transform that map into a struct as soon as possible. Structs are basically tagged maps with a weak schema, and structs can help reify domain concepts and are really great for pattern matching. And they basically ensure at compile time that only the specified keys are allowed to exist on this map. For example, defining functions that pattern match in structs is kind of like type checking. Here we have two simple structs, and the modules that back them pattern match on the struct type in their functions. The team struct at the bottom is a really simple extraction that is a collection of employees. And we have functions here to raise a single employee salary or to raise all of their salaries by their percentage. So you can be sure that your functions will only work with specific structs that have that specific schema that you want. Now tuples shine when they're used as tag tuples. And even more so when you combine them with pattern matching, for example, many standard library functions return tag tuples by default. And the bang version of the function, if it has one, will raise an error if it fails. And in Elixir 1.2 and beyond, you can also use the with keyword, which works really nicely with tag tuples since you don't have to make your function directly pattern match that tag tuple. You can instead extract the value from the tuple and then pass that into your function. Finally, there is also a special form of tuples combined with lists, known as keywords. You might be already familiar with a similar concept in Ruby. They're best used as options for functions and macros, and they're not really recommended to use for anything else. So speaking of structs, one cool thing about them is that you can also define protocols for them. Structs combined with protocols are a way to achieve polymorphism in Elixir, which basically means you can act in something in a generic way without knowing specifically what that thing is. So let's build up that team struct example from before with a simple protocol. So let's define an implementation of count for our team struct so we can see how many employees are in this team. And you can do this using the def implementation keyword inside the body of the module. And now, if you use the enum count function on the struct, you can get the number of employees. And if we had actually defined all the necessary implementations, which are count, member, and reduce, then our team struct would effectively become a kind of innumerable, even though it's really a struct with key values. To take it a bit further, let's fully implement the innumerable protocol. The member implementation returns a tag tuple with the error atom and a module reference. So it'll refer to the default membership algorithm that is in Elixir. And the reduce implementation just grabs the employees key of the struct and uses the default list reduce function. And now, we can use all our innumerable functions on our team struct, which is really cool. Many of the innumerable functions that ship with Elixir use the reduce function under the hood, so you don't actually have to implement every single function to make your struct an innumerable. Only count, member, and reduce are actually required. And that's why we had a function on our team module to promote all the employees inside it. Now, we can clean up this implementation since it's an innumerable. And instead of mapping over the employees key in the struct, we can just map over the team itself. And you no longer need to care how the struct is internally structured. While protocols work with data types like structs, behaviors are a way to specify interfaces for callbacks in modules. For example, you might want to validate some data coming in from an external request. So let's start by using really simple struct to represent that request. And we want to validate that request to make sure it has the stuff we want on it. And to do this, we might want to write a validator. For example, we might want to validate that some value is present on the request, or it's a number, or so forth. And what we need to do is, in our validator module, we define a behavior using the at callback, at macro callback, and at optional callbacks keywords. And now we can implement one of these validators that must implement that interface we just defined. And here is a really simple implementation of a presence validator. And if you want it to treat empty maps and lists as not present, then you can do so. And we have to implement all the non-optional callbacks for our behavior. Otherwise, you get warnings when compiling, but you don't get compiler errors. If you run Dializr, which is a static analysis tool for Erlang, you can also check to ensure that the callbacks were implemented correctly. And it's quite straightforward to get Dializr working with your Elixir project. There is a wrapper around Dializr called Dialixr, which is straightforward to add to your mixed project. And using TypeSpecs as a tool to do static type checking can be very useful and help you catch errors before they happen. There's a blog post link at the bottom of this slide that shows an example of TDD, test ribbon development using TypeSpecs, which I thought was really interesting. Finally, let's look at macros. One of the coolest and most powerful language features of Elixir. Metaprogramming in Elixir is a first-class citizen. And if you're not familiar with it, it's code that writes code. And it can be a very powerful tool. For example, Ecto provides a bunch of macros to create a DSL domain-specific language for SQL queries. And Phoenix provides a DSL as well for using macros in its router. And you may or may not know this, but Elixir itself is actually written in Elixir as macros. When I first realized this, I was a little bit confused. Like, how can you write a language in itself? That doesn't make sense. But it turns out Elixir uses a compiler technique known as bootstrapping, which happens here in Erlang. And the bootstrapping then used to start defining the primitive macros that make up Elixir. This bootstrap compiler only understands the basic building blocks of Elixir. And these include keywords like def, defp, defmodule, defmacro, and some others. And then that's enough to basically just start writing Elixir in Elixir, which is just really cool. For example, there's a module known as kernel specialforms. This module defines the basic building blocks, like map and list literals, and protects them from being overwritten by other macros. And our Erlang compiler will basically take our Elixir code and call that relevant Erlang module. For example, a map literal would get passed through here and the second Erlang function will match the Elixir maps in text, which eventually creates the map itself, while the function at top will update maps with that pipe syntax. And you probably already know that you can call Erlang modules in Elixir. For example, these modules are available as atoms, like math. And you can see that the Elixir map literal that we created is equal to a new map created in Erlang using its maps library. And that's how you know Elixir maps are actually Erlang maps under the hood. So now let's try to make our own simple macro. I'm a big fan of this command line utility called calse. Basically, it takes some text as arguments and it returns a picture of a cow saying it, which I think is awesome. So let's define a really simple macro that does that. Here we define this macro using with double underscores, also known as dunder, which gets called when you bring in the module to another module using the use keyword. This macro will then define the function on the consuming module at compile time. So now if you use this calse module in another module, that macro will get called and define our function. And now you can call bob.calse with some text. And it renders in our terminal, which is pretty cool. I think this could be a revolutionary Elixir library. So look out for it. Anyway, there was a very basic example of macros. I didn't really go into any detail at all, but if you're interested, you can read Chris McCord's book. I think he knows a thing or two about macros. This book is really good and tells you pretty much everything you need to know. All right, so now on to Phoenix. I titled this section Phoenix is Not Rails, because it's easy to be tempted to write a Phoenix app the same way you would another app like, for example, a Rails app. But often it's better to start with a fresh perspective, especially when you go from an object-oriented language to a functional one. And I hope this section will help you see that. So once you're familiar with the basic building blocks of Elixir, Phoenix is a lot easier to pick up. After all, it's just an Elixir library. Phoenix takes the Elixir concept we've learned and expands upon them. And I'll use a Phoenix API as an example to explain what I mean. So Phoenix is made mostly up of pipelines. And a pipeline is composed out of plugs. One of the main bits of Phoenix is in the endpoint module. And this is responsible for receiving a connection, parsing that request, adding headers and session information like cookies. And then it hands it off to your router, which routes the connection to a controller action, like get or show or index. The controller does it thing, then passes the connection down to the view where it might render the response or even halt it if something went wrong. And here's a diagram that might better illustrate what happens when a user makes a request to your API. You start at the endpoint, then your router, then you might have a pipeline in your router, for example with authentication, then in your controller, and then to the view. And you could even think about Phoenix in the big picture like this. It's obviously more than composing a simple function with the pipe operator, but the idea is the same. So yeah, remember Phoenix is not your app, it's just the web component of your app. So basically Phoenix is a pipeline, a pipeline made up of smaller pipelines and functions. Many of these are plugs, which I'll get to in a minute, and the smaller bits are functions that you define in your application, and these are the lower level operations. With Phoenix, you have a very functional way of building an API, and everything is a function, including Phoenix itself. And once you realize this, it becomes simple to reason about, and there's no magic, and you can very easily introspect or read the source to see how it works. So I mentioned plugs. Plugs are one of the core abstractions from Elixir. A plug is a direct interface to the underlying web server. If you're familiar with Ruby, plug is kind of like rack, but not really, because with plug, there is no middle where there's only plugs. And there are two kinds of plugs. This one's a function plug, which takes in a connection and options and returns to connection. Or you might have a module plug that implements the init and call callbacks. The call function itself is a function plug until it returns a connection. And it's a very simple but powerful contract. And once you know this contract, you can write your own custom plugs very easily, and you can make use of them not only in your router as pipelines, but you can also, for example, use them in controllers before a connection is routed to an action. Here, the verify request signature plug is called before any action is handled by this controller. And you can even add guard for this plug to be only called for specific actions. Plugged pipelines in the Phoenix router are very composable. For example, let's add another pipeline for our API to follow the JSON API specification, which is a shared convention for building JSON APIs. And we do this using the JAW serializer library. Then as an example, let's make two scopes for our router. The V1 scope is nested inside the API scope. And the foo route, which is in the API scope, will only go through that pipeline since it's in that scope. And whereas the user routes will go through the JSON API and authorize the pipeline since it's nested inside. And if you remember the endpoint module I mentioned earlier, it's actually just a big plug pipeline. So everything from logging, JSON parsing, to adding hitters and session information like cookies, it all happens here before the connection even hits your router. And would you be surprised if I told you that a Phoenix controller is itself a plug? I don't think it's that surprising. And in fact, it's just plugs all the way down, which is really cool. So in some sense, Phoenix is fractal, or in other words, each part of it has the same characteristics as the whole. So zooming out a little bit, a couple of weeks ago, I wrote a blog post about using Phoenix to replace your current API. But committing to a full rewrite is really difficult, expensive, and disruptive, and probably not the best use of your time. But what if you could do that rewrite incrementally, one endpoint at a time? You could try out Phoenix without significant buy-in, and you could see for yourself if a rewrite is even worth it. And now that we know how Phoenix works on a high level, it should be relatively straightforward to figure out how to do this. So let's explore how I wrote this plug with my ex-colleague and friend, Dan McLean. Terraform, which is the name of the plug, forward to unmatched routes to your current API while it serves up your Phoenix API if it does find a match. So you can very easily implement one or more endpoints in Phoenix and forward everything else to your current API, almost like a reverse proxy. So our first hunch was to try and put the plug for the router in the endpoint module since that's where everything happens. And then in our plug, we would look up all the routes on the router to see if we needed to forward the request or to handle it in Phoenix. But this turned out to be a really bad approach because, for one, it uses private API, which is usually frowned upon. And it was also very inefficient because you had to check every single request whether or not it needed to be forwarded. So that was a good first attempt, but I think we can do better so we went back to the drawing board. Now whenever I get stumped, I find it helpful to look at other people's code for ideas and we had a peek at the source for the error handler plug in the plug repo. And Dan realized we could use the same idea in our plug, that is to only handle the missing route errors. And because that was overwriteable, we could use the same macro, but update the catch to do our forwarding. And so our plug was born and we chose the name Terraform because a cool metaphor for an incremental rewrite. Kind of like terraforming an uninhabitable planet to a habitable one except this time it's an API. So in this plug, we have the using macro that gets called when the consuming app uses it in the router. Then we look for the option which points to a module in the Phoenix application. And we create a module attribute for it so we can access it later. Then the before compile macro gets run and we pass the connection in the catch to our Terraformer which is itself a plug. So now in your router you can use Terraform and pass it a reference to your Terraformer module using the keyword. Terraformer is basically a fancy word for a plug that handles how that unmatched request is forwarded to your legacy or current API. And if you use the plug router in this plug, you also get a really nice DSL for routing macros that you are probably familiar with like get, put, post, and et cetera. And you can basically just do all the forwarding to your API here. For example, you might wanna match a specific path or you might wanna handle all get requests and then send that to your API and you can add authentication headers, cookies, and so forth. And the response from your API will then get sent back to the user. So Terraformer is actually a really nice way to do incremental rewrites using Phoenix without too much risk. Now the hardest part about learning Elixir is resisting the urge to bring in familiar OO concepts that you don't necessarily need in a functional language. Before we continue, I wanna say that I don't hate OOP. I think it has its use case and I've had a lot of success with it. But from my time spent with Elixir, I personally think it's far harder to shoot yourself in the foot of a functional language, at least for me. And when I go back to working on our Ruby and Rails API, it's so much harder to understand what's going on sometimes. Now, if you've done any OOP, you've probably heard of the design pattern and you've probably implemented some of them as well. But it's not always clear what pattern is best suited for the problem. And sometimes it feels like multiple patterns can solve your use case or maybe none of them. And unless you have years and years of OO experience, it's easy to fall onto the trap of over or even misusing these abstractions. There's a quote I really like about choosing the right abstraction by Sandy Metz and I think it applies both to OO and FP, but perhaps even more so when attempting to solve problems with OO design patterns. And it actually turns out there is a design pattern for functional programming too, which is very straightforward. This is a great talk by Scott Lawshan from the F-sharp language. And the next couple of slides are very heavily influenced by talk. So if you have some time, you should watch his talk. So the biggest thing in my opinion when you transition from OO to FP is figuring out what to do with state. In object-oriented programming, you deal with this using objects and instances. In the context of JavaScript, it would be using the disk keyword in a method. For example, you might have a person class and in this example, a person instance from this class can do three things. It can greet, it can report and it can move. The classic example is the first instantiate your person class, and now we have an instance of that class with those methods. And in this case, the person instance will initialize with coordinates of zero and zero. Moving this person will update their internal state, and now when you call report again, the coordinates have changed accordingly. And if you wanted to add another class that can also move, you might want to extract that logic into a concern. For example, this movable class would let us achieve polymorphism and any class that extends from it will have that same move and report methods. On the other hand, in functional programming, instead of state, we think about data and we think about transforming that data. And in elixir state, it's typically held in a process, but I am not going to talk about that today. Instead, I want to talk about functions. Functions are standalone, they're not in elixir, they're not attached to classes as methods and a function takes an input, it does something of it and returns an output and that is the main contract of a function. For example, in OO, you might use a method to convert an apple into a banana. And let's say we wanted to convert this banana into a pineapple. It might blow up because you didn't define such a function, which by the way is a really great error message for a space suit. But what about in a functional language? We solved this problem with composition and you can't tell that the composed function was built from smaller functions. So a useful metaphor for OO is that it tries to model the system and models their interactions within that system. While in functional programming, we're encouraged to think about transformations for data instead. I mentioned earlier that Phoenix is fractal and the same can be said of for functional programming. Again, the simplest example is our function. If we have enough of those functions, we can use the value from one of them as inputs to another. And by doing so, we form services, not microservices, but services. Microservices are basically microservices without the micro. And for example, we might make an email validator, which is itself composed by small, low level functions. Now we take our services, we compose them and we end up with use cases. For example, a user might want to update their profile. And with enough of those use cases, we compose them together and we end up with a web application. A HTTP request goes in and a HTTP response comes out. And if you recall, this is exactly the philosophy of Phoenix. We take a connection and we return a connection. And with that, I hope the concepts have come full circle now. So to recap, it's always a good idea to start with the basics first. Pipes and pattern matching are very powerful when combined. Recursion is a really great tool for some situations and using the right data type is key. Protocols and behaviors help us make our code easier to work with and macros are very powerful metaprogramming tools. Phoenix is a pipeline made up of smaller pipelines and plugs. It's plugs all the way down and fractal in nature. Plugs are useful abstractions that wrap connections and return connections, making them very composable. And understanding how plugs work helped us make that Terraform plug for incrementally replacing your API with Phoenix. In OO we have state and functional programming we transform data. In OO we model the system. In functional programming we design transformations of data. Functional programming is very fractal. A low-level function is composed with other low-level functions to form services, which compose to form use cases, which compose to form our web app. You can keep in touch with me on Twitter or on GitHub. I'm actually not gonna do Q&A today because I'm terrible at it. So please come speak to me later or send me a DM on Twitter if you have a question. Thank you for listening. You've all been great.