Functional Programming

Introduction to Elixir for Rubyists

Introduction to Elixir for Rubyists

by Josh Adams

In the talk "Introduction to Elixir for Rubyists," Josh Adams provides an overview of Elixir, a functional programming language that builds upon the Erlang VM and shares similarities with Ruby. The session begins by introducing the origins of Elixir, created by José Valim, a prominent Ruby developer, as a response to evolving programming needs, specifically for concurrent and fault-tolerant applications. Adams emphasizes key aspects of Elixir’s syntax and features, showcasing differences compared to Ruby. He covers essential concepts such as:

  • Data Types: Elixir includes atoms, numbers, strings, lists, tuples, maps, and regex, with similarities and differences outlined in comparison to Ruby. For instance, strings in Elixir are not objects and utilize Unicode effectively at compile time.
  • Pattern Matching: A distinctive feature where variables are bound to values, affecting how functions are defined and making it useful for control flow with constructs like case statements.
  • Functions: Functions in Elixir are first-class types, which can be anonymous and use pattern matching to simplify logic. Adams illustrates function invocation and matching.
  • Modules and Mix: Modules are the primary organization units for code in Elixir, akin to classes in Ruby. The Mix tool is showcased for starting new projects and managing dependencies, akin to Ruby on Rails.
  • List Comprehensions and Structs: List comprehensions allow streamlined data manipulation, while structs provide type-safe maps that facilitate pattern matching and default values.
  • Concurrency and Processes: Adams highlights that processes in Elixir are units of concurrency, emphasizing that they share no memory, communicate asynchronously, and efficiently utilize multi-core systems. He provides a performance comparison, demonstrating Elixir's ability to handle 40,000 concurrent requests, vastly outperforming Ruby.
  • Testing and OTP: He briefly discusses testing in Elixir with XUnit and the importance of built-in documentation testing. Finally, the talk touches on the OTP framework for building resilient, distributed applications.

In conclusion, Adams conveys that Elixir is designed to enhance programmer happiness and efficiently handle modern programming challenges, particularly in the realm of concurrency. He opens the floor for questions, inviting further discussion on the topics presented.

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.