Talks

Introduction to Elixir for Rubyists

Elixir is a concurrency-oriented programming language built atop the Erlang VM. Its syntax is very Ruby-influenced, and it takes some great features from the Python world as well. In this talk, I'll provide a quick introduction to the language. I'll provide just a quick overview of the language syntactically, as well as cover some areas where it differs wildly from Ruby.

Ancient City Ruby 2014

00:00:00 Alright, so I'm Josh Adams, as you said, and I'm going to explain to you as Rubyists.
00:00:05 I'll primarily do this by showing you a lot of the syntax and the primitives.
00:00:11 I'll compare it to Ruby where that's appropriate.
00:00:17 If you've never heard of this language, let me start with a brief introduction.
00:00:24 Who knows who José Valim is? Okay, so a bunch of people.
00:00:30 He's a Rails core developer, very substantially known as a Ruby developer.
00:00:38 In 2011, he took a sabbatical from Rails core and started working on a secret project.
00:00:44 Elixir is that project.
00:00:49 It's a functional programming language built on top of the Erlang VM.
00:00:55 To give you the official Elixir sales pitch,
00:01:00 Elixir is a functional and metaprogramming language built on top of the Erlang VM.
00:01:06 It's a dynamic language with flexible syntax and macro support.
00:01:11 It leverages Erlang's abilities to build concurrent, distributed, and fault-tolerant applications with hot code upgrades.
00:01:17 Alright, that was a bunch of stuff, right?
00:01:24 I'm going to try and touch on all these topics, but there's limited time, so let's go ahead and get started.
00:01:30 I'm not going to kick off this talk saying, "Hey, you should learn Elixir" because I think it's really neat.
00:01:36 But I'm not above getting people whose opinion you might value more than mine to do it for me.
00:01:42 So, Dave Thomas, pragmatic programmer, has repeatedly said that Elixir gives him the feel-goods the way Ruby did when he first started learning it in 1998.
00:01:49 He's seen more than a few programming languages, so he kinda knows what he's talking about.
00:01:55 He's currently writing a book called "Programming Elixir," which is an introductory text for learning Elixir.
00:02:01 Now, I'm going to mention Bruce Tate.
00:02:05 Has anyone here heard of his book "Seven Languages in Seven Weeks"?
00:02:11 He points out that we're making a transition to multi-core.
00:02:17 He goes on to say that if we don't switch to something other than mutable state, things are going to break.
00:02:24 I agree with this absolutely.
00:02:30 I recently built a distributed system in Ruby using Celluloid, which is very cool.
00:02:36 However, building it in Ruby was really frustrating.
00:02:43 Celluloid is great, but Ruby libraries often do really bad things with mutable state.
00:02:49 Typically, they do this in a not concurrency-safe manner.
00:02:56 The killer problem I faced was related to our specs and mocks.
00:03:03 There were host situations where I ended up writing a library instead of using existing Ruby libraries.
00:03:09 One of the big reasons for using Ruby is its incredible library support.
00:03:15 There are libraries to do everything.
00:03:21 But one of the downsides is that none of those libraries care about threads.
00:03:27 I would frequently find a library that did exactly what I wanted, and then upon digging into it,
00:03:33 I discovered it was using a very bad and not thread-safe way.
00:03:39 So I ended up rewriting them constantly.
00:03:46 It's fairly understandable if you attended last year's Rubicon.
00:03:53 Matt said last year that he's not a threading guy.
00:03:59 And that's understandable; we all do our own thing.
00:04:05 But it was incredibly frustrating for me because I was dealing with all of this at the time.
00:04:12 I was really hoping that, when he was asked the question, he'd come up with something amazing.
00:04:20 I was hoping we were going to implement these concurrency primitives in the language.
00:04:27 But that’s not happening for a while.
00:04:34 So that’s the Elixir sales pitch.
00:04:40 Now, why am I qualified to tell you about this stuff?
00:04:46 First off, I'm the CTO of Isotope 11, a software consultancy.
00:04:53 We've been doing Ruby client work for eight years.
00:05:01 We've also become very comfortable with JavaScript, Erlang, and Elixir.
00:05:07 By the way, we have two projects wrapping up this week.
00:05:13 So we're looking for new awesome projects if anybody needs help.
00:05:19 I wouldn’t be talking to you if I didn’t work for this company.
00:05:26 I also run a screencast series called Elixir Sips.
00:05:34 For one of the best questions I receive, I'm going to give away a subscription.
00:05:40 It basically chronicles my journey learning Elixir.
00:05:46 There are currently around 63 videos and about seven hours of footage.
00:05:53 I've been doing this for seven months, so I've spent a considerable amount of time learning Elixir.
00:06:00 Having said that, let's get into it.
00:06:08 In this section, we'll cover some basic concepts.
00:06:15 We'll look at data types, pattern matching, functions, modules, and Mix,
00:06:22 which you can think of as a build tool and dependency manager.
00:06:29 Let's get started.
00:06:35 The data types we'll cover are atoms, numbers, strings, lists, tuples, maps, and regex.
00:06:41 Atoms in Elixir are similar to symbols in Ruby.
00:06:48 They look pretty normal and consist of a colon followed by letters, digits, and underscores.
00:06:54 Atoms are used frequently to tag tuples.
00:07:01 You'll often see them as the first element in a list or tuple.
00:07:07 Strings in Elixir are exactly like strings in Ruby, except they're not objects.
00:07:14 After reading an article on Hacker News about Unicode support in various languages,
00:07:20 you'll be glad to know that Elixir outperforms most other languages in this area.
00:07:27 Elixir's Unicode support is programmatically built at compile time,
00:07:34 based on the Unicode text code points file.
00:07:41 Now, let's discuss basic data types.
00:07:47 You can use underscores in numbers and the compiler will ignore them.
00:07:56 You can add, subtract, and compare them. Floating point numbers can have a decimal point,
00:08:02 but you cannot omit a zero in front of them.
00:08:09 Lists look like arrays in other languages, including Ruby, but they have different semantics.
00:08:18 To create a list, simply put items in square brackets.
00:08:24 The easiest and fastest way to interact with a list is to get its head or its tail.
00:08:30 This means getting either the first element of the list or all of the rest.
00:08:39 Lists are actually linked lists, so there are a couple of built-in functions for accessing the head and tail.
00:08:46 These functions are HD for head and TL for tail.
00:08:53 If you're looking for something similar to arrays, you want tuples.
00:09:00 Tuples are ordered collections enclosed in curly braces.
00:09:06 They are often used in pattern matching and as return values from functions.
00:09:12 While lists are implemented as linked lists, tuples are contiguous areas of memory.
00:09:20 This allows for constant time access, which is useful.
00:09:25 Now let's talk about maps.
00:09:31 Maps are like hashes, and they just landed in Erlang's 17th release candidate.
00:09:37 As of this writing, they aren't even stable in Erlang yet.
00:09:43 However, they're expected to improve performance in future releases.
00:09:50 Updating a map has interesting behavior and syntax.
00:09:56 You can address the items in a map using dot syntax if the keys are all atoms,
00:10:02 or with your access syntax.
00:10:09 The syntax for updating looks a bit quirky at first.
00:10:15 It uses the first element as the map, followed by a pipe, and then the update.
00:10:21 Elixir has Perl-compatible regular expressions.
00:10:27 You can use them with the same syntax you're used to in Ruby.
00:10:35 Regular expressions support more advanced use cases, including named captures.
00:10:41 For example, capturing a section and retrieving it as a key list.
00:10:47 In summary, everything except false or nil is considered truthy in Elixir.
00:10:54 Next, we'll look at pattern matching, which is one of the neatest features of Elixir.
00:11:01 We're going to talk about the match operator, function definitions, and case statements.
00:11:06 The match operator looks like an equal sign.
00:11:13 At first glance, it looks like regular variable assignment, but it's a bit different.
00:11:20 It sets the previously unbound variable to a value.
00:11:27 If you're trying to match with an unbound variable, Elixir will bind the variable so your assertion is true.
00:11:35 If the variable is already bound and you attempt to match a different value, it'll throw an error.
00:11:41 This is a matcher.
00:11:46 You'll see this often as you get started with Elixir.
00:11:52 Now, let's explore how matches work with more complex data structures.
00:12:01 For instance, using an underscore tells the compiler you don't care about a value.
00:12:06 Elixir is a compiled language, as opposed to an interpreted one.
00:12:12 In Elixir, you can rebind variables, which might confuse those used to Erlang.
00:12:19 This allows you to rebind a variable without static single assignment.
00:12:25 If you want to prevent rebinding of a variable, you can put a hat before it to signal this to the compiler.
00:12:31 Pattern matching is also used to choose between different function definitions.
00:12:37 In the example, we define a function differently based on the input.
00:12:44 This is a significant difference from Ruby's approach.
00:12:50 Defining functions based on how they operate based on inputs is exciting, especially for those with a math background.
00:12:56 Next, case statements can be used for control flow in a similar pattern.
00:13:03 There's a clear precedence, moving from top to bottom.
00:13:09 Let's now move on to functions.
00:13:16 In Elixir, functions are first-class types, which shouldn't be surprising since it's a functional language.
00:13:23 We'll discuss defining, calling, and using them as types.
00:13:31 Anonymous functions are defined using the 'fn' keyword.
00:13:37 The parameter lists and bodies are separated by arrows.
00:13:44 The 'print name' function simply concatenates first and last names.
00:13:54 Calling an anonymous function looks a bit unusual at first.
00:14:00 The argument looks similar to calling a function in the module.
00:14:06 However, a dot makes calling it feel different.
00:14:13 It’s under discussion whether this will change.
00:14:19 Initially, I disliked it, but I've grown to accept it.
00:14:27 Now, what happens if you call this function with something that doesn't match any of the parameter lists?
00:14:34 It throws an error indicating 'function clause not matching'.
00:14:40 Now, let's define an anonymous function that behaves differently based on input.
00:14:47 If you provide a list of two items, it returns the sum of their prices.
00:14:54 For a single item, it simply returns the price.
00:15:00 Though a very silly way to define this function, it demonstrates pattern matching.
00:15:07 You can invoke anonymous functions immediately.
00:15:13 This action highlights some fun utility.
00:15:20 Next is a focus on modules and Mix.
00:15:29 Modules are the primary unit of code organization in Elixir.
00:15:36 They function like classes in Ruby.
00:15:43 Modules can contain both private and public functions.
00:15:50 Let’s discuss using Mix to begin new projects.
00:15:56 With Mix, type mix new <project_name> to start a new project.
00:16:03 This is very similar to 'rails new.'
00:16:09 Mix also knows about Git, creating a Git file for you.
00:16:16 It automatically sets up testing, which is first-class in Elixir.
00:16:22 Next, let's look at the mix.exs file generated by Mix.
00:16:29 This file holds project configuration.
00:16:36 You can define dependencies here.
00:16:44 For example, using a tuple that takes a dependency name and a GitHub location.
00:16:51 Mix fetches and compiles the dependencies...
00:16:57 including those from Erlang or even C dependencies.
00:17:02 It's worth noting that the Mix tool is incredibly powerful.
00:17:08 Let's define a trivial example of a module with a single function.
00:17:15 This simple function just returns its argument.
00:17:22 Remember, you can define multiple functions with the same name.
00:17:29 Elixir uses pattern matching to differentiate between the function definitions.
00:17:38 Now, let's compile this module.
00:17:45 You can launch the Elixir shell (IEx) and load the module.
00:17:52 You can also use elixirc to generate a compiled bytecode file.
00:17:58 This bytecode can run on the Erlang VM, allowing interoperability.
00:18:05 To see it in action, define a module in IEx.
00:18:12 This approach is surprising for Erlang users.
00:18:19 You can also define modules directly inside the REPL.
00:18:26 Now let’s explore the last evaluated expression of a module.
00:18:33 This tuple contains details regarding the last action.
00:18:40 Elixir supports module and function documentation.
00:18:46 Documentation can be defined using module attributes.
00:18:52 You can ask for help on any module using a helper in IEx.
00:18:58 This outputs helpful information about the module.
00:19:05 You can also generate HTML documentation.
00:19:12 Those are some of the fundamental insights into basic Elixir.
00:19:20 We've covered data types, pattern matching, functions, and modules.
00:19:27 From here, we'll shift focus to slightly less basic Elixir.
00:19:34 We'll talk about list comprehensions, structs, the pipe operator, recursion, and processes.
00:19:39 The general idea behind list comprehensions is to quickly create a data structure.
00:19:46 For example, from one or more enumerable lists.
00:19:52 Here's a basic example: for each X in a list, return double the X.
00:19:58 It's straightforward! In another example, we return a different structure.
00:20:04 We can also filter lists, only including X's divisible by 2.
00:20:10 List comprehensions can combine lists, resulting in a larger collection.
00:20:16 This functionality makes it more versatile than Ruby.
00:20:22 The output from comprehensions makes them even more interesting.
00:20:28 You can create a list of tuples with conditions based on multiplicative calculations.
00:20:35 Let's define a module for building a deck of cards.
00:20:42 We define the suits and values, returning a card table.
00:20:49 Structs are type-safe maps that can have default values and can be pattern matched against.
00:20:55 Structs are straightforward to define, we just use '__struct__'.
00:21:01 Behind the scenes, a struct is essentially an Erlang map.
00:21:08 Elixir was designed for programmer happiness, similar to Ruby.
00:21:15 For structs, the update syntax allows you to maintain type safety.
00:21:21 For example, we can verify updating the person struct with a last name.
00:21:28 Trying to update a raw map will throw an error.
00:21:34 Pattern matching on structs is easy.
00:21:41 If the last name matches, we greet the brother, otherwise, we do not.
00:21:47 Now, let's discuss the pipe operator, which is quite useful.
00:21:54 The pipe operator allows you to create a sequence of function calls.
00:22:01 In UNIX, a pipeline takes the output from one process as input into another.
00:22:07 Elixir's pipe operator offers similar powerful data transformations.
00:22:14 In code, the pipe operator looks like this: |>
00:22:21 This allows you to pass data through multiple functions seamlessly.
00:22:27 For example, I built a code removal project using Elixir's pipe operator.
00:22:34 The pipeline structure is clear and easy to read.
00:22:40 If we didn’t use the pipe operator, the format becomes cumbersome.
00:22:47 Jose is very focused on creating a syntax that prioritizes readability.
00:22:53 Now let's briefly cover recursion.
00:23:00 Recursive functions call themselves.
00:23:06 For instance, we can write a recursive function that sums a list of numbers.
00:23:12 The primary function has two forms, using an accumulator.
00:23:19 It processes by popping the head and calling itself.
00:23:25 The final form handles an empty input list.
00:23:32 Elixir supports tail call optimization, avoiding stack overflow.
00:23:39 It allows for efficient recursive operations on large lists.
00:23:45 Now let's discuss processes.
00:23:52 In Erlang, a process is a concurrency unit.
00:23:59 Processes share no memory and communicate via asynchronous messaging.
00:24:05 Any message sent is a copy, ensuring no shared state.
00:24:11 In Elixir, there are no blocking processes.
00:24:18 If you create many processes, it runs multi-core by default.
00:24:24 For example, running on a four-core machine distributes processes across cores.
00:24:31 I performed a performance test using Elixir, serving up to 40,000 concurrent requests.
00:24:37 In contrast, Ruby only managed around 300.
00:24:43 Elixir also supports easy distribution across machines.
00:24:50 You can increase system performance by adding up to 15 or 20 machines.
00:24:57 This is an incredible advantage for high-performance systems.
00:25:04 Now, let’s define a new module called ping.
00:25:10 This module contains a function that awaits pong messages.
00:25:17 When it receives a message, it responds with a ping.
00:25:23 When spawning a new process, the process ID is captured.
00:25:30 This ID is essential for communicating via messages.
00:25:36 Processes maintain a mailbox for received messages.
00:25:43 It’s important to understand that messages are stored in a mailbox.
00:25:50 You can spawn processes across different machines seamlessly.
00:25:56 Everything is consistent, whether local or remote.
00:26:03 Modeling mutable state is accomplished through processes.
00:26:09 By modifying the state, you create an object-like variable.
00:26:16 Each message can operate just like calling methods on objects in Ruby.
00:26:23 Like in Ruby, you're sending messages to processes.
00:26:30 We defined a mechanism to communicate and modify state.
00:26:37 Unfortunately, there’s a lot more to explore.
00:26:44 Today we've covered list comprehensions, structs, pipes, recursion, and processes.
00:26:50 There are areas that we didn't have time for, and I think they're important.
00:26:57 For example, testing is vital.
00:27:06 Elixir ships with XUnit, its built-in testing framework.
00:27:13 It's similar to TestUnit in Ruby.
00:27:19 You can also run documentation tests to ensure accuracy.
00:27:25 Elixir's documentation matches the expected output from examples.
00:27:32 This feature is essentially a one-liner to verify documentation.
00:27:39 Let's talk about OTP.
00:27:46 It's a framework for building distributed fault-tolerant systems.
00:27:53 We organize applications into supervision trees that manage processes.
00:28:01 If any process fails, it can be restarted automatically.
00:28:07 This ensures high uptime and stability.
00:28:14 Now, distribution allows processes to be created across different machines.
00:28:20 It's a powerful aspect of both Erlang and Elixir.
00:28:27 That's all for my talk!
00:28:33 I'm now open to any questions you might have.