00:00:24.880
If you could everyone, come up here and move your arms around a bit. It's great!
00:00:30.560
Let me start off by explaining something: I love Ruby. I've been working with Ruby since 1998. I've revised the Pickaxe book four times, and in fact, the latest revision came off the presses just last week. There are actually four copies lying around somewhere. So, I have a commitment to Ruby—don't get me wrong.
00:00:45.520
I'm not here to say that all you Ruby programmers shouldn’t be here or that it's not worthwhile. That's not the case.
00:00:58.480
However, I also think that we owe it to ourselves, all of us, to keep an eye on what's going on in the programming landscape. If we don't, as Sandy and I were discussing over lunch, we're eventually going to be out of date.
00:01:10.560
How many people here used to go, or still go, to the No Fluff Just Stuff conferences? A few? Not many? I'm actually kind of proud of the fact that I've been banned from speaking at those, because I used to attend a set of Java conferences and talk about Ruby. They said, "Oh, you can't do that anymore; go talk about Java." But I told them, "No, I'm not going to talk about Java," so here I am at a Ruby conference talking about Elixir.
00:01:33.040
Why am I talking about Elixir? To me, Elixir is like a gateway from what I used to do to what I will be doing. I've been looking for this for 10 years, and I finally found it.
00:01:50.560
The reality is that the future is functional, and the future is concurrent. These two technologies combined are pretty much the only way we can continue to develop software effectively. We're running into all sorts of obstacles in traditional software development.
00:02:05.280
How many times have you heard someone say, "Oh, that’s not going to work"? We want to achieve 100% reliability.
00:02:21.440
For the longest time, I couldn't figure out what he had to do with it, but you know the saying that every year you get twice as ugly? I don't know why that came to mind, but does he—or does he not—look like the Wendy’s Dave Thomas guy?
00:02:44.320
Anyway, the reason he's cheery is because he's worth four billion dollars! He came up with the infamous Moore's Law. Now, Moore's Law is not actually what most people claim it to be. Moore's Law says that the most cost-effective density for putting transistors onto a silicon chip doubles every two years.
00:03:01.600
It's nothing to do with performance or anything else; it just states that Moore believed he could pack twice as many transistors onto a chip and hit the optimal point for cost-effectiveness.
00:03:17.040
I was trying to remember the first microprocessor I ever used, and it was definitely the 8008. I also used the 6502, which is on the lower end of the graph here, both of which had maybe 3,000 transistors.
00:03:38.720
The y-axis on this graph is exponential, just as it was in Sandy's graph on books.
00:03:54.560
Nowadays, my laptop has a Core i7 processor in it, which is unbelievable. Just last night, when I got these numbers, I couldn't believe that my laptop has roughly a billion transistors in this processor.
00:04:11.360
That’s a billion transistors! Some guys actually measured the size of the processor chip, because Intel doesn’t release those specs, and worked out that it’s about 121 square millimeters.
00:04:27.520
That means I have roughly eight and a quarter million transistors per square millimeter in my processor. That’s almost unimaginable! If you want to know how big a square millimeter is, I found a use for this: If you take the cross-section of a typical paper clip, it’s about a square millimeter.
00:04:43.840
That means in that little space, there are 8.25 million transistors.
00:05:06.720
But what that really means in practice is that you can't use a billion transistors the same way you would use ten thousand. With ten thousand transistors, you can coordinate them and have them all working on the same problem at the same time. However, with a billion transistors, that's not possible.
00:05:23.040
The speed of light gets in the way of coordinating all of them. This is why we're moving more towards multi-core and hyper-threading inside the cores to give us parallelism in our processes.
00:05:40.960
We've kind of hoped the problem would go away because right now we can sort of get away with writing our Rails code on our servers and running multiple processes, and it kind of works. But it kind of doesn't.
00:05:58.960
You all know the issues we face when we try to run that way.
00:06:10.240
So for those reasons, I think we're looking at a sea change. In order to continue to exploit the growing curve of Moore's Law, we're going to have to find new ways of writing software. I believe that functional programming is one of those new ways.
00:06:27.520
Elixir, I think, is functional, concurrent, pragmatic, and fun. Let's spend a bit of time looking at the functional aspects.
00:06:37.600
Initially, I had, like, I don't know, a hundred slides or something, but I thought, screw that; let me just code instead. So I'm just going to code some stuff up, and you will get to see how bad a typist I am. Together, we may learn something.
00:06:54.720
The first thing I'm going to do is actually go into the shell, which in Elixir is just like IRB. It’s called IEx. Well, I say it’s like IRB, but it’s nothing like IRB.
00:07:11.680
Right now, I am orchestrating roughly 25 independent processes in my Elixir shell here. The compiler is running in a separate process, and the IEx command line is running in another process. They’re all communicating back and forth, which is kind of cool.
00:07:30.080
It’s totally transparent. Just like with IRB, I can do things like type an expression, and it works. If I assign a value to a variable, for example, when I say a = 1, I'm not really assigning to a variable at all.
00:07:47.760
In a functional programming language, all I'm doing is asserting that a and 1 both have the same value. Because of this, I can also say 1 = a, and it will respond with 'Yep, no problem at all.'
00:08:02.560
However, if I say 2 = a, that now gives me something called a 'no match error.' The reason for this is that Elixir will only bind new values to variables if they’re on the left-hand side of an equal sign.
00:08:20.960
Since a already has the value of 1, Elixir states that whatever's over on the left must also equal 1.
00:08:38.320
Now, I’ve talked about pattern matching. This is an absolutely critical aspect of any functional programming language. Let's have a quick look at pattern matching; we’ve already seen the case of a = 1.
00:08:58.320
Here we have a pattern where some variable is equated with a scalar, which will assign the variable to have the same value as that scalar.
00:09:17.520
I could also use an Elixir list instead. Now my variable a has been associated with that list [1, 2, 3]. But pattern matching goes quite a bit deeper than that. I can say, define d, e, f equal to a. What this does is say: on both sides, I'm expecting to see a list of three elements.
00:09:37.760
As luck would have it, a is indeed a list of three elements, so this will match and assign 1 to d, 2 to e, and 3 to f.
00:09:58.720
We can take this further; I can take a list like a and split it into its head and its tail. I could say: head followed by tail in a list equals a, and if I do that, then the head will be 1, and the tail will contain the rest of the list.
00:10:14.560
So, pattern matching allows me to construct and deconstruct my data, and as you will see later, it also allows me to write larger programs without any conditional logic.
00:10:34.560
Typically, that's also done without any loops. All I'm doing in my code is simply declaring stuff.
00:10:50.160
Let's start off by writing some Elixir code. I'm going to use Sublime Text and write a quick bit of Elixir, defining a module called 'sequence,' because eventually it will be... oops!
00:11:06.240
So all Elixir code goes into a module. This isn't strictly true; however, when you want to compile Elixir code, you do need to put it into a module.
00:11:18.480
That's because Elixir runs on the Erlang Virtual Machine, and that’s how the Erlang VM manages its code. You may have heard that Erlang allows you to do hot swapping of code; well, so does Elixir.
00:11:37.920
In a running application, I can replace it at the module level, which is why I need to put my code in a module.
00:11:53.360
So, I can write a method called 'hello' and just say hi. Later down here, I can call it by saying sequence.hello.
00:12:06.640
If I run that code, wow! You would be hard-pressed to think that that was Ruby, right? It’s very, very similar.
00:12:21.440
In fact, Elixir takes a bunch of things from Ruby and runs with them. For example, the 'def' keyword looks like a function definition, which it kind of is, but actually it's also a function call.
00:12:38.720
In Elixir, 'def' is a function call, just like 'defmodule.' This function takes two parameters: the name of the method and the body of the method.
00:12:53.760
It just so happens that in Elixir, there’s a bit of syntactic sugar that lets you write the body as 'do ... end.' Underneath, that actually gets translated into something else.
00:13:06.960
The 'do' in the function is just like a Ruby hash, a named parameter that I'm passing in. For example, an if statement in Elixir—if I write 'if a equals b, do something, else do something else'—is also just syntactic sugar.
00:13:25.760
Because 'if' is also just a method—actually, it's a macro—but effectively, it’s a method that takes three parameters: the expression, a 'do' block, and an 'else' block.
00:13:42.080
And that means I can rewrite the entire language if I want to. Everything is soft; when you talk about meta programming, this is serious meta programming.
00:13:59.280
Unlike other languages you might name, it's done in a very controlled style. First of all, meta programming in Elixir is done by manipulating the parse tree, which happens to be an Elixir data structure.
00:14:19.200
It's done in a hygienic way, so it will not overwrite any of your local values accidentally, and it’s done in a scoped way.
00:14:36.160
In Elixir, every change you make to the environment, whether defining macros or including a module, is lexically scoped.
00:14:51.200
So for example, I can say, "Apply this different version of a particular operator only to the parameters of this method call." That can come in very handy.
00:15:05.680
However, that's not why we're here. Let's have a look at something slightly different—let's explore lists.
00:15:20.800
I want to be able to say something like: IO.puts sequence.length of some list.
00:15:35.520
There are built-in ways of doing this, but let’s do it the hard way. I need to define a method called 'length' that’s going to give me the length of the list.
00:15:51.520
In a functional programming language, I'm going to use pattern matching to do most of the work. So, the length of an empty list can be defined using pattern matching: an empty list will equal 0.
00:16:06.720
That takes care of one case. The other case is for a non-empty list with a head and a tail, where the tail could be empty. We know the head contributes 1, and the length of the tail is simply the length of the tail.
00:16:24.239
Does this make sense? It’s a nice little recursive definition.
00:16:36.960
If I run that, it blows up, but that’s okay. Oh, kernel.length conflicts with a local variable. Look at that; although it’s untyped, it does a fair amount of work to ensure that, for example, I haven't misspelled function names or accidentally overwritten function names.
00:16:53.440
So, let’s call that 'len' instead of 'length.' There we go.
00:17:09.440
A couple of things here: first of all, there's my result: 5. Notice the variable head is unused; because when I did the pattern match, I set head to be the head of the list and tail to be the tail of the list, but in my code, I only ever use tail.
00:17:23.520
Elixir has a convention that if I put an underscore in front of a variable name, it says, 'I don't plan to use this variable.' By doing that, my warning goes away.
00:17:38.880
I could, in fact, just say underscore here, and that just works, but I personally prefer to leave the variable name because it documents what I’m doing.
00:17:55.440
So, that’s a typical recursive definition. You don’t find yourself doing those too often. One thing that I like about this from an aesthetic point of view is that my definitions are pretty much the specification of what I want to write.
00:18:12.960
For example, 'The length of an empty list is zero.' Someone could tell you that, right? And 'The length of any other list is one plus the length of the rest of the list.' That’s a specification.
00:18:27.200
Let me show you another specification that’s useful for my purposes: Fibonacci numbers—1, 1, 2, 3, 5, 8, etc. Each number is the sum of the previous two numbers.
00:18:43.760
If you go to a math book or Wikipedia, they will tell you the definition of the Fibonacci function: the Fibonacci of 0 is 1, the Fibonacci of 1 is 1, and the Fibonacci of any number n is the Fibonacci of n minus 1 plus the Fibonacci of n minus 2.
00:19:02.080
That is the mathematical definition of Fibonacci.
00:19:18.560
But to convert that into running Elixir code, all I have to do is write it like this. Now, if I say sequence.fib of 20... there it is! Is that cool?
00:19:34.080
Now at this point, you're probably saying, 'But Dave, that’s all very well, but how many times a day do you actually calculate the Fibonacci series?' Okay, no more than five or six times, I have to tell you.
00:19:48.160
But that's okay, because the reality is that this form of expressing code I do all the time. In fact, when I'm writing Ruby code, I typically work out the various conditions and write them down; not as tidily as this, but I'll write things like '0 is 1' or 'an empty request means I send back a four.'
00:20:05.920
I do all those kinds of things in Ruby, and I do the same in Elixir. But it just turns out that coincidentally, I'm writing code. That's really cool!
00:20:21.840
Let’s look at a few more examples. First of all, let’s define a map function, which is similar to the Ruby map function: I want to take a collection and apply a function to each element in that collection.
00:20:38.720
So I'm going to start off with the use, because that's always a good way to do things. So in this case, I will do IO.puts sequence.map.
00:20:53.440
Okay, so I’m going to map through a list: let’s say [4, 5, 6, 7], and I'm going to map it through my Fibonacci function.
00:21:09.440
My Fibonacci sequence is going to be... Oh, I should do it this way.
00:21:27.920
Let's say I want to define a function that takes a number n, and then its body is to calculate sequence.fib of n.
00:21:46.480
So my map function needs a collection and a function. What are my special cases here? I think it’s the same as the 'length' example we did: if I want to map over an empty collection, then the result will also be an empty collection.
00:22:02.560
Now, we’re dealing with the case of mapping over a non-empty collection, so we’ll do that head-tail thing again.
00:22:19.440
We get the function, apply it to the head of the list because that's part of what I need to do, and then I have to generate the rest of the list that has been mapped.
00:22:36.880
I can express that by saying 'fun.head,' so that’s calling the function on the head, then I'm going to build the rest of the list by mapping on the tail.
00:22:55.120
That map function is the same map function that I defined earlier, and what's going to happen is that each time around this recursion, it's going to take one extra element off the list.
00:23:09.920
It will convert that element by running it through the function and build a new list where that element is included. Eventually, it will come to this clause and terminate the recursion.
00:23:27.440
In theory, if I run this code, it should output correctly. Okay, let me change this to be an inspect.
00:23:43.040
Alright, I just fell foul of some Erlang quirks. But anyway, there’s my mapped list. It’s really quite straightforward and very understandable code. It is recursive, but so what?
00:24:00.000
Here, I've written a map function and a Fibonacci function, and in both cases, I have no explicit loops and no explicit conditional logic. That’s fantastic!
00:24:18.720
So, you might say, 'But Dave, you’re doing testing by inspection. Can we do better than that?' You bet we can! So let's do something like test basic functions.
00:24:34.560
I need to use ExUnit, the testing framework, and I actually have to start the testing framework because remember about the multiple processes: it runs in its own separate process.
00:24:49.040
Now I can write some tests. Test basic Fibonacci function works. Alright, I should start my assertion.
00:25:05.840
Sequence.fib(10) should equal 10946.
00:25:20.720
And I’ll test basic map function to see if it works.
00:25:35.840
Now let's showcase a few examples here.
00:25:44.640
Now, you’ll see what happens when I run that.
00:26:00.920
Oh dear, I have made a typo; bad me.
00:26:09.840
So those are basic unit tests written using the ExUnit framework. I will see what happens when I run that.
00:26:25.920
It says function test2 undefined. I’m sorry, where do I get the function done?
00:26:36.640
Oh, I see; it’s not 'use ExUnit,' it’s 'use ExUnit.Case.' That’s why! Let’s run that.
00:26:53.600
Oh, it seems my test failed, and I'm expecting 5, 8, 13, but got 5, 8, 13, 21.
00:27:01.440
In most testing frameworks, I'd have to say assert equals, just to compare results, but in Elixir, here my test function intercepts calls to equals and inside that block.
00:27:16.320
It runs the original equals, but it still has access to the two operands as a code fragment. It can take that stuff, convert it back into text, and use it here.
00:27:30.560
That is pretty cool! Just think of the cool things you can do with that.
00:27:48.080
Now, let's push the boat out. I talked about it being a parallel language, a concurrent language. Let’s come back to our sequence map and see if we can make it parallel.
00:28:02.720
I have five minutes left, is that right? God help me!
00:28:20.040
I’m going to write a parallel version of map. Let’s call it pmap, which will take a collection and a function.
00:28:38.560
What it will do is this: I'll show you how I think about functional programming. It will take the collection, run each of the elements of this collection in a separate subprocess.
00:28:54.560
So, we're going to call it spawn children instead of function just to save myself a little typing. After we spawn each process, we’re going to collect the results.
00:29:10.560
What’s this pipe operator here? It’s like a pipeline, just like in a UNIX shell. I can pipe the output of one thing to the next. Here, I've taken my collection and I'm piping it into spawn children, and the results that come back I'm going to pipe to collect results.
00:29:25.920
This is significant not just for the saving in typing; it changes the way I think about programming. I think about programming as mapping from A to B rather than doing something.
00:29:43.120
So, I have a collection, and I'm going to map it into a set of processes, and then I’m going to map those processes into some results. It’s a transformation, not an explicit imperative way of coding.
00:30:00.080
How do we code those things up? I know I’m running out of time, so I'll do this rapidly. I have my spawn children function, and it will take a collection and a function.
00:30:17.120
It will take the collection and map it through a function called spawn a child, spawner for each child.
00:30:33.360
Clearly, I need to pass it the element from the collection and the function. So, I could do something like this: element, fun, and then wrap this in an anonymous function.
00:30:50.280
But because that’s such a common pattern, Elixir has a shortcut for this. I can map it through spawn children, where the first element is the first parameter passed in, and Elixir automatically wraps that into an anonymous function.
00:31:06.760
This anonymous function is nothing more than a call to a named function, and Elixir rewrites that into a direct function call.
00:31:21.840
What's my spawn child function going to look like? It receives a value and a function. This gets a little tricky because we're going to spawn ourselves and make ourselves run in a separate process.
00:31:39.960
To run a separate process in Elixir, I'll call spawn and choose the most verbose method, for clarity. I’m going to spawn a function in my current module called child.
00:31:55.520
I’ll pass three parameters: the value, the function, and myself, where 'self' refers to the current process.
00:32:11.920
The child will send me a message back to say, here’s the value. Now I need to write the child function. I actually like writing one-liners in Elixir.
00:32:27.840
My child method will receive a value, a function, and a pointer to self, which is actually the parent of the child.
00:32:43.600
It will send a message back to the parent using the Elixir arrow notation, indicating which message to send, which will include the PID and the function, as in: value = fun.(value).
00:33:00.120
Now back to the original pmap at the top: I've done the first part of spawning a separate process for each value in my collection, and it’s running independently.
00:33:16.880
Now I just have to collect the results back. I will define a collect results method that takes a set of process IDs. I need to receive the results in order.
00:33:31.520
I'm using a receive block. This means I will receive a message and use pattern matching to control what message I receive.
00:33:54.800
In this case, I want to receive the message from a specific PID. I haven’t defined that yet, but I will shortly.
00:34:07.280
My collect results method will take a PID as a parameter. I’m mapping through the PIDs and defining 'collect results for PID,' which takes a PID as a parameter.
00:34:22.440
I’ll run into any issues when I run this. If I run out of time or it's running for the first time, that seems unlikely.
00:34:35.920
Let’s run this and see what happens with any luck. So far, there are no errors, and pmap still functions correctly.
00:34:50.320
It seems to be working! Let’s change this to pmap instead of map.
00:35:07.520
So now it works cool just to ensure you see that clearly, and I know I'm going to get the hook.
00:35:24.320
I'm going to show you one more cool thing—if I take that file called, what an imaginative name that is, I have loaded it into my Elixir.
00:35:41.040
I can call sequence. I should import sequence to save myself typing.
00:35:56.560
So now I can say fib(30) and get a result. Now, if I run fib through a regular map, say for 30, let's see what's that?
00:36:12.560
It's giving me trouble now with larger numbers. When I run this through pmap, let’s see...
00:36:27.760
Now I’m using all of my CPU cores, and all of them are busy.
00:36:42.960
That’s cool! At this point, I could go on for days, but then they won’t let me come back.
00:37:02.080
So, I want to thank you all. I’m done! Thank you!