40,81 €
Leverage the power of Elixir programming language to solve practical problems associated with scalability, concurrency, fault tolerance, and high availability.
Key Features
Book Description
Running concurrent, fault-tolerant applications that scale is a very demanding responsibility. After learning the abstractions that Elixir gives us, developers are able to build such applications with inconceivable low effort. There is a big gap between playing around with Elixir and running it in production, serving live requests. This book will help you fll this gap by going into detail on several aspects of how Elixir works and showing concrete examples of how to apply the concepts learned to a fully fledged application. In this book, you will learn how to build a rock-solid application, beginning by using Mix to create a new project. Then you will learn how the use of Erlang's OTP, along with the Elixir abstractions that run on top of it (such as GenServer and GenStage), that allow you to build applications that are easy to parallelize and distribute. You will also master supervisors (and supervision trees), and comprehend how they are the basis for building fault-tolerant applications. Then you will use Phoenix to create a web interface for your application. Upon fnishing implementation, you will learn how to take your application to the cloud, using Kubernetes to automatically deploy, scale, and manage it. Last, but not least, you will keep your peace of mind by learning how to thoroughly test and then monitor your application.
What you will learn
Who this book is for
Mastering Elixir is for you if you have experience in Elixir programming and want to take it to the next level. This Elixir book shows you how to build, deploy, and maintain robust applications, allowing you to go from tinkering with Elixir on side projects to using it in a live environment. However, no prior knowledge of Elixir is required to enjoy the complex topics covered in the book.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 753
Veröffentlichungsjahr: 2018
Copyright © 2018 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the authors, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Commissioning Editor: Richa TripathiAcquisition Editor: Sandeep MishraContent Development Editor: Anugraha ArunagiriTechnical Editor: Jash BavishiCopy Editor: Muktikant GarimellaProject Coordinator: Ulhas KambaliProofreader: Safis Editing Indexer: Pratik ShirodkarGraphics: Tania DuttaProduction Coordinator: Shantanu Zagade
First published: July 2018
Production reference: 1280718
Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.
ISBN 978-1-78847-267-8
www.packtpub.com
To Ana and José
To Raquel and Gabriel
Mapt is an online digital library that gives you full access to over 5,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Mapt is fully searchable
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
André Albuquerque is a software engineer at Onfido, after working in the banking industry for seven years. He has a master's degree from Instituto Superior Técnico in distributed systems and software engineering, and, during his banking detour, he obtained a master's degree in economics. He is currently developing Onfido's microservices using Elixir and Ruby, learning every day about how applications can score and scale if we apply the correct tools and sound coding practices from the get-go. In his time off, he loves to build his own keyboards, play basketball, and spend time with his wife and son.
Daniel Caixinha is a software engineer at Onfido, where he is using Elixir to build resilient systems that can also handle the high growth of the business. After graduating from Instituto Superior Técnico, he joined the startup world, mainly using Ruby, but also got the chance to play around with Elixir. Upon joining Onfido, he got the chance to take Elixir more seriously, which made him fall in love with functional programming in general, and Elixir in particular. Besides building Elixir applications, he is fostering the use of Elixir, being also a member of the Lisbon Elixir meetup.
Paulo A Pereira is a senior software engineer. He fell in love with Elixir and has a passion for exploring new technologies and keeping himself up to date with the industry's developments.
He previously worked as a consultant and lead developer for Mediadigital, implementing Grails and Rails solutions, and is currently working at Onfido Background Checks, a London-based tech start-up that is proving to be a key player in the background checking industry
If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
Title Page
Copyright and Credits
Mastering Elixir
Dedication
Packt Upsell
Why subscribe?
PacktPub.com
Contributors
About the authors
About the reviewer
Packt is searching for authors like you
Preface
Who this book is for
What this book covers
To get the most out of this book
Download the example code files
Conventions used
Get in touch
Reviews
Preparing for the Journey Ahead
Why functional programming?
Elixir and Erlang
Elixir's data types
Integers
Floats
Atoms
Booleans
Tuples
Lists
Maps
Binaries
Strings and charlists
Other types
Keyword lists
Ranges
MapSets
Pattern matching
Pattern matching on tuples
Pattern matching on lists
Pattern matching on maps
Pattern matching on binaries and strings
Functions and Modules
Anonymous functions
Modules and Named Functions
Module attributes, directives, and uses
Working with collections
Looping through recursion
Eager processing with the Enum module
Comprehensions
Lazy processing with the stream module
Control flow
if and unless
cond
case
with
Exceptions
Typespecs and behaviours
Typespecs
Dialyzer
Behaviours
Protocols
Structs
Bringing structs and protocols together
Tooling and ecosystems
IEx
Mix
ExUnit
Erlang interoperability
Summary
Innards of an Elixir Project
Elixir applications
Elixir project structure
Project dependencies
Umbrella projects
ElixirDrip – our showcase application
Using behaviours
Viewing cross references with xref
Adopting a consistent coding style
Summary
Processes – The Bedrock of Concurrency and Fault Tolerance
Inside the BEAM
Working with processes
Creating processes
Message passing between processes
Building a cache worker
Detecting errors by linking and monitoring processes
Using links
Using monitors
Recovering from errors with supervisors and supervision trees
Defining and starting a supervisor
Streamlined child specs
Starting children at runtime using dynamic supervisors
Minimizing error effects with supervision trees
Summary
Powered by Erlang/OTP
GenServer
GenServer at your service
Implementing a CacheWorker with GenServer
Agent
Task
Parallel tasks
Using (D)ETS
Disk-based ETS
Registry
Summary
Demand-Driven Processing
GenStage
The upload pipeline
The notifier stage
The RemoteStorage stage
The Encryption stage
The Starter stage
The download pipeline
The Notifier stage
The Encryption stage
The RemoteStorage stage
The Starter stage
Final remarks
Flow
Sample data
Lazy queries
Disk-usage rank query
Summary
Metaprogramming – Code That Writes Itself
The abstract syntax tree
Tinkering with macros
Hygienic accesses
Famous (un)quotes
Macros – a silver bullet?
Extending modules
Using the use and __using__/1 macros
Creating a domain-specific language
Registering module attributes
Collecting a list of pipeline steps
Generating worker specs and subscription options
Producer stage
ProducerConsumer and Consumer stages
Collecting the pipeline worker specs
Defining the supervisor functions
Streamlining GenStage modules
Simpler pipeline producers
Simpler pipeline (producer) consumers
Macros cheat sheet
Summary
Persisting Data Using Ecto
Connecting to the database
Schemas and migrations
Schemas
Custom field types
Users or media owners?
Schema or not – that is the question
Migrations
Relationships
Changesets
Media
Users
Media ownership
Constraints
Queries
Media folder queries
Loading schemas and their associations
Queries with raw SQL
Finding top users with aggregates
Summary
Phoenix – A Flying Web Framework
The Plug specification
Creating a module plug
Creating a function plug
Routing requests
Handling requests in a controller
Rendering views
Layouts
Authenticating users
Implementing a JSON API
Authenticating users in the API
Interactive applications using channels
Preparing the server
Joining channels on the client
Exchanging events
Authenticating socket connections
Tracking users statuses using Presence
Summary
Finding Zen through Testing
Unit testing
Testing functions without side-effects
Testing functions with side-effects
Creating mocks with Mox
Testing interactions with the repository separately
Testing the documentation
Integration testing
Testing Phoenix Channels
Testing macros
Property-based testing
Summary
Deploying to the Cloud
Releasing with Distillery
Configuring the release
Interpolating environment variables
Creating the release
Creating a custom release task
Containerizing our application
Creating a development container
Orchestrating more than one container
Composing the deployment containers
Deploying to Kubernetes
Configuring the cloud database
Creating a namespace
Creating secrets
Publishing the production image
Deploying your first pod
Creating a Kubernetes job
Exposing your pods to the world
Continuous deployment with Travis CI
Connecting the deployed Elixir nodes
Testing the connected nodes
Summary
Keeping an Eye on Your Processes
Collecting metrics
Exposing Prometheus metrics
Creating custom Prometheus metrics
Local Prometheus server
Deploying Prometheus in Kubernetes
Calculating percentiles
Setting Grafana variables
A window to your nodes
Connecting to a containerized node
Connecting to an Erlang node running in Kubernetes
Using a remote shell
Inspecting application behavior
Profiling with the fprof profiler
Tracing with the :dbg module
Summary
Other Books You May Enjoy
Leave a review - let other readers know what you think
Running scalable, concurrent, and fault-tolerant applications is a demanding endeavor. After learning the abstractions that Elixir offers, developersareable to build such applications effortlessly. That being said, there is a big gap between playing around with Elixir and running it in production and serving live requests. Mastering Elixir helps you to fill thisvery gap, and it not only goes into detail about how Elixir works, but also guides you to putthe learned conceptsto good use with the help of concrete examples.
In this book, you will learn how to build a rock solid application, beginning by using Mix to create a new project. You will then explore how to use Erlang’s OTP as well as the Elixir abstractions thatare builton top of it, allowing you to build applications that are easy to parallelize and distribute.Having got to grips with the basics,you will master Supervisors and comprehend how they are the basis for building fault-tolerant applications.In addition to this,you will understand how to use Phoenix in order to create a web interface for your application.
Upon finishing implementation,you will study how to thoroughly test and monitor the developed file server.Toward the end,you will learn how to takethisapplication to the cloud, using Kubernetes to automatically deploy, scale, and manage it.
Mastering Elixir is for you if you’re a programmer who has some experience in using Elixir and want to take it to the next level. This comprehensive guide shows you how to build, deploy, and maintain robust applications, allowing you to go from tinkering with Elixir on side projects to using it in a live environment. However, no prior knowledge of Elixir is required to enjoy the complex topics covered in the book.
Chapter 1, Preparing for the Journey Ahead, starts our odyssey by introducing Elixir. It starts by covering the data types of Elixir and how pattern matching works, moving on to then explore how to create modules and functions, while also seeing, among other things, how to work with collections and use control flow in Elixir. After reading this chapter, you will be familiar with Elixir's syntax, and how to use it to write some simple functions.
Chapter 2, Innards of an Elixir Project, kicks off by analyzing what makes an Elixir application and what an Elixir project is. It then examines how to use the Mix tool to create projects and manage their dependencies. It's here that we'll create an umbrella project for the ElixirDrip application that will be developed throughout this book.
Chapter 3, Processes – The Bedrock for Concurrency and Fault-tolerance, starts by exploring how the BEAM VM works and its concurrency model, the actor model. Then, it shows how to create and work with processes in Elixir, building a piece of the ElixirDrip application as an example. This chapter also explores how to link and monitor processes, and how Supervisors build on top of this to detect crashing processes. Finally, this chapter explores how we can group Supervisors to create supervision trees, which enable the creation of fault-tolerant applications.
Chapter 4, Powered by Erlang/OTP, introduces OTP and examines both the battle-tested OTP abstractions that Elixir inherited from Erlang, such as GenServer and Erlang Term Storage, and the new Agent, Task and Registry abstractions that Elixir brought to life. Each abstraction is put to good use by implementing a media cache, a text search function, and a search results cache.
Chapter 5, Demand-Driven Processing, goes a long way to explain how the GenStage and Flow abstractions introduced by Elixir let the developer process data using a demand-driven paradigm. Instead of processing data as fast as possible to match the rate at which data is produced, this approach turns the problem on its head and forces the producer to inject new data into the pipeline at a rate controlled by the data consumers. It's in this chapter that the upload and download pipelines of the ElixirDrip application are developed.
Chapter 6, Metaprogramming–Code that Writes Itself, unveils the constructs that allow the developer to easily control what happens in compile time, by writing regular Elixir code that produces more code. The chapter starts by iterating on a macro whose purpose is to measure the time a function takes to execute, and ends by implementing a Domain-Specific language which significantly simplifies the media upload and download pipelines defined in the previous chapter.
Chapter 7, Persisting Data Using Ecto, explores how to work with databases. It starts by explaining how to connect an application to a database and how database structure changes can be applied as migrations. It then examines how to create and enforce table relations and how to use changesets to persist and update data. In the end, different ways to query the database are analyzed.
Chapter 8, Phoenix: A Flying Web Framework, introduces the Phoenix framework and explains how to use it to add a web layer to our ElixirDrip application. The chapter begins by the conventional topics, such as routers, controllers, and views, but toward the end, also explores more progressive concepts, such as building a JSON API or using Phoenix Channels.
Chapter 9, Find Zen through Testing, dives into different kinds of testing. It begins with unit and integration testing, and goes on to explore more elaborate topics, such as how to test macros and Phoenix components, and also how to write property tests.
Chapter 10, Deploy on the Cloud, takes the ElixirDrip application running to its live environment, storing media from real users. This chapter starts by explaining how to use Distillery to package the application in an efficient way and then introduces the concept of application containerization and how it suits the development and deployment phases of our application. The last half of the chapter examines how to deploy the application to a Kubernetes cluster and,ultimately, how to automate this process by using a continuous integration service.
Chapter 11, Keeping an Eye on Your Processes, teaches you how to monitor your application so that you can ensure that your application is working as it should. You will begin by learning how to collect metrics from your application. Then, we will teach you how to use an Erlang tool, called Observer, which allows us to tap into what is going on inside the BEAM virtual machine. You will learn how to get statistics from the BEAM (such as CPU and memory utilization), as well as from the state of each process inside your application. Lastly, we’ll look at how to inspect our application and check what’s happening under the hood. You will learn how to investigate a bottleneck by profiling our ElixirDrip application, while also checking how to use Erlang’s standard library to trace calls to a certain process.
As mentioned earlier, no prior knowledge of Elixir is required to read this book. In the initial chapters, we provide an introduction to Elixir, covering all the necessary concepts to be able to follow the more advanced topics explored later in the book.
To get the most out of this book we recommend that you follow along each chapter, by building and running the application we will develop in this book. To build this application, we have used free and open source software as much as possible. When this was not possible, we have used software with long trial periods, so that you can build and run the application on your own.
You can download the example code files for this book from your account at www.packtpub.com. If you purchased this book elsewhere, you can visit www.packtpub.com/support and register to have the files emailed directly to you.
You can download the code files by following these steps:
Log in or register at
www.packtpub.com
.
Select the
SUPPORT
tab.
Click on
Code Downloads & Errata
.
Enter the name of the book in the
Search
box and follow the onscreen instructions.
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:
WinRAR/7-Zip for Windows
Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux
The code bundle for the book is also hosted on GitHub at https://github.com/PacktPublishing/Mastering-Elixir. In case there's an update to the code, it will be updated on the existing GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "In Elixir, you create a process by calling the Kernel.spawn/1 function".
A block of code is set as follows:
IO.puts("Hello, World!")
Any command-line input or output is written as follows:
$ touch hello_world.ex
Bold: Indicates a new term, an important word, or words that you see on screen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: "Select elixir from the Processes tab."
Feedback from our readers is always welcome.
General feedback: Email [email protected] and mention the book title in the subject of your message. If you have questions about any aspect of this book, please email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/submit-errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packtpub.com.
Welcome to this incredible journey! This is the beginning of an odyssey that will take you through the many features of Elixir, and how to use them to build, test, deploy, and maintain applications. This journey may require some shifting of your mindset and how you think about programming (and problem-solving in general) if you're not already familiar with functional programming.
Before diving into this book, we want to point out that this introductory chapter is deliberately short. Although we'll be introducing the language, its tooling, and its ecosystem, this won't be a complete reference guide. Elixir treats documentation as a first-class citizen, and this is shown in the incredible documentation of its modules and functions. Hence, we decided to teach you how to search for what you need, and target on the core concept of book–building an Elixir application. We think that this pragmatic approach is the one that delivers the most value to you, as you'll be able to follow an application right from its inception and into its production, including its deployment and monitoring, which are sometimes overlooked.
In this chapter, we will cover the following topics:
Data types in Elixir
Working with pattern matching and how to apply it to various types
Working with functions and bundling them in modules
Working with collections
Using classic control-flow constructs (such as
case
)
Using typespecs on your functions
Creating behaviours, and adopting them in other modules
Using protocols to make our functions polymorphic
Some of the most useful tools that ship with Elixir
Calling Erlang libraries directly
Interacting with operating system processes through ports
Coming from an object-oriented background ourselves, we can't emphasize enough how valuable this shift is. Adopting the functional programming paradigm brings along some benefits, such as the following:
The data-transformation flow becomes more evident. This is in contrast with what usually happens in object-oriented programming, where objects strive to encapsulate the data; in functional programming, we mutate data using functions. This makes the transformations that are applied to the data explicit, which in turn makes the applications written this way easier to understand.
Functions are (mostly) side-effect-free. With immutable data, you can be sure that the value of a certain variable you hold a reference to will remain the same, since if another function or process wants to change it, it has to create a copy first—and operate on that copy. This makes code much easier to analyze and comprehend, since you can rest assured that your variables will remain as you expect. However, note that since Elixir isn't a pure functional language, your code can still have side effects. For instance, if a function writes to a file, it creates a side effect.
Programs can be parallelized easily. Coming out as an added bonus of immutability, it's usually very simple to parallelize this type of program, since there is no shared state.
One possible disadvantage of having immutable data is that you can incur a performance penalty, as each time you need to change something, you must make a copy of it. This greatly depends on the implementation, and while this concern is generally valid, Elixir employs clever techniques when compiling your code to minimize this effect. For instance, in certain conditions, Elixir can just point to existing variables when creating new ones, as it knows all variables are immutable and will never change.
If you're overwhelmed with all this functional programming jargon, lie down and relax, as we will explore these concepts in greater detail throughout this book.
Elixir, created by José Valim, runs on the Erlang VM (also known as BEAM). Erlang, developed at Ericsson more than 30 years ago, was created to improve the reliability and scalability of the company's telecom systems. Nowadays, it is used in a number of different settings, from database systems to chat applications. Erlang has fault-tolerance and distribution baked into its design, and is famous for running systems with nine nines of reliability.
Erlang's runtime is natively distributed, given that it was designed to be deployed on multiple telecom switches simultaneously. Programs running on the Erlang VM can take advantage of this by easily distributing an application across multiple nodes, but also across multiple CPUs—since multiple cores is just a specific case of a distributed system. This is an incredible selling point of the Erlang VM (and thus of Elixir), since in today's setting CPUs are not getting much faster, and we're instead seeing CPUs with an increasing number of cores coming out.
In this opening chapter, we will be introducing Elixir. Beginning with its data types, we will also look at pattern matching, anonymous and named functions, modules, and some control-flow constructs. Then, we will see how to work with collections, and then we will briefly touch on behaviours and protocols. The chapter will end with an overview of the incredible tooling Elixir provides, along with some ways to exploit the existing interoperability between Elixir and Erlang.
We will now describe Elixir's data types, which extend upon Erlang's data types. Elixir is a dynamic programming language. Consequently, you don't declare the type of each variable—it depends on the value it holds at each moment.
To improve the learning experience, we'll be providing some examples along the way. For now, we'll just use Elixir's REPL, IEx (short for Interactive Elixir). To start an IEx session, you must have Elixir installed on your machine. Elixir has an official page with instructions on how to do this if you don't have it installed, whether using package managers, the precompiled version, or compiling from the source yourself:
http://elixir-lang.github.io/install.html
Provided that you have Elixir already installed on your machine, type iex on your terminal to start a new IEx session. With this, you can run the examples present in this chapter in your machine. Note that your default iex prompt contains a number in between parenthesis, which represents the number of expressions you've entered in the current session, such as iex>(1). To declutter the output, in our examples, we've removed this number.
We'll be exploring IEx in greater detail toward the end of this chapter, in the Tooling and ecosystems section. Throughout the following subsections, we'll be mentioning some built-in modules in Elixir. We'll explore what modules are in the Functions and modules section—for now, it's enough to know that a module is a collection of functions.
This type contains, as you would expect, numbers that can be written without a fractional component. The size of integers adjusts dynamically according to its magnitude—you don't have to worry about this: an integer will simply occupy more words in memory as it grows. Here's some basic arithmetic with integers:
iex> 25 + 8
33
To improve the readability of the code, you can also use underscores in between the digits of an integer, as shown here:
iex> 1_000_000 - 500_000
500000
Besides decimal, Elixir also supports integers written in binary, octal, and hexadecimal (using 0b, 0o, and 0x, respectively):
iex> 0b10001
17
iex> 0o21
17
iex> 0x11
17
In Elixir, floats are written with a decimal point, with digits before and after it, meaning that .1 is not a valid float in Elixir (as it is, for instance, in JavaScript). In Elixir, you have to be explicit and write the leading 0—so in this case, you'd write 0.1. Here's an example of the multiplication of two floats:
iex> 0.1 * 0.5
0.05
You can also write floats using the exponent notation, as shown:
iex> 0.1e3
100.0
Floats are represented in IEEE 754 double precision, which yields between 15 to 17 significant decimal digits. As usual, you should take care when comparing floats for equality.
Atoms are a constant, whose value is its own name. They are always prefixed with a leading colon (:), followed by alphanumeric characters (and possibly _ or @). They may terminate with an exclamation or a question mark. Atoms are similar to enumerations in C and symbols in Ruby. Here are some examples of atoms:
iex> :ok
:ok
iex> :error
:error
iex> :some_descriptive_name!
:some_descriptive_name!
iex> :value@start
:value@start
You can create atoms with arbitrary characters with the following syntax:
iex> :"Atom name with arbitrary characters#$%^"
:"Atom name with arbitrary characters#$%^"
As with all data structures in Elixir, atoms can't be modified after their creation. Furthermore, they are not garbage-collected. Atoms are kept in the atom table, and upon compilation, their value is replaced by a reference to their entry on this table. This makes comparing atoms very efficient. As you'll learn throughout this book, this is one of the major use cases for atoms in Elixir, as we are constantly matching the return of a function against a certain expected atom.
Lists are created by wrapping the elements we want inside it with square brackets ([ and ]), separating the values with commas. Internally, lists are implemented as singly linked lists, meaning that accessing the elements of a list is a O(n) operation. Lists aren't stored contiguously in memory as arrays in other languages. As with tuples, list elements can be of any type:
iex> [1, :an_atom, 0.5]
[1, :an_atom, 0.5]
We have the ++ and -- operators that are exclusive to lists, and serve to concatenate and subtract lists, respectively:
iex> [0, 1, 1] ++ [2, 3, 5]
[0, 1, 1, 2, 3, 5]
iex> [0, 1, 1] -- [1, 2, 3]
[0, 1]
To check whether a certain element is present in a list, you can use the in operator:
iex> 1 in [0, 1, 1, 2, 3, 5]
true
iex> 99 in [0, 1, 1, 2, 3, 5]
false
To get the head of a list, we use the hd function, whereas to get the tail of a list, we use the tl function:
iex> hd([0, 1, 1, 2, 3, 5])
0
iex> tl([0, 1, 1, 2, 3, 5])
[1, 1, 2, 3, 5]
Notice that the semantic of tail here is the list without its head (which is also a list), and not the last element of a list. We'll be exploring this concept in more depth, along with some more examples on how to work with lists, in the Working with collections section. For reference, you can find a detailed list of operations you can make on lists at https://hexdocs.pm/elixir/List.html.
A binary is group of consecutive bytes. You create them by surrounding the byte sequence with << and >>. Here we are creating a two-byte binary:
iex> <<5, 10>>
<<5, 10>>
In the decimal base, a byte can only contain values up to 255 (otherwise it overflows). If we want to store values greater that 255, we need to tell the runtime to use more space to store this binary:
iex> <<5, 256>>
<<5, 0>>
iex> <<5, 256::16>>
<<5, 1, 0>>
As you can see, when we specify the size (16 bits in this case) we can see that the output as an extra byte and the overflow didn't occur. The size doesn't have to be a multiple of 8. In that case, a binary is usually called a bitstring.
Most programmers will not handle data at such a low level, so your use of binaries may not be that frequent. However, they're extremely useful in certain scenarios, such as processing the header of a file to find a magic number and identify the file type, or even when dealing with network packets by hand.
Strings are binaries with UTF-8 codepoints in them. You create a string with the usual double-quote syntax:
iex> "hey, a string"
"hey, a string"
Charlists are, as the name implies, lists of character codes. You create them using the single-quote syntax:
iex> 'hey, a charlist'
'hey, a charlist'
Since this is just a list, you can use the hd function to get the code for the first character:
iex> hd('hey, a charlist')
104
Both representations support string interpolation:
iex> "two plus two is: #{2+2}"
"two plus two is: 4"
iex> 'four minus one is: #{4-1}'
'four minus one is: 3'
Both representations also support the heredoc notation, which is most commonly used to write documentation. To create it, use three single or double quotes:
iex> """
...> a string with heredoc notation
...> """
"a string with heredoc notation\n"
iex> '''
...> a charlist with heredoc notation
...> '''
'a charlist with heredoc notation\n'
Elixir provides sigils as another syntax to declare strings or charlists, which can be handy if you want to include quotes inside your string. You can use ~s to create a string and ~c to create a charlist (their uppercase versions, ~S and ~C, are similar but don't interpolate or escape characters):
iex> ~s(a string created by a sigil)
"a string created by a sigil"
iex> ~c(a charlist created by a sigil)
'a charlist created by a sigil'
There's another sigil that's worth mentioning: ~r, which is used for regular expressions. In the next snippet, we're using the run function from the Regex module to exemplify the usage of the ~r sigil:
iex> Regex.run(~r{str}, "a string")
["str"]
iex> Regex.run(~r{123}, "a string")
nil
You can find the list of supported sigils (and also how to create your own!) at http://elixir-lang.github.io/getting-started/sigils.html.
The convention in the Elixir community is to only use the term string when referring to the double-quote format. This distinction is important, since their implementation is very different. Functions from the String module will only work on the double-quote format. You should always use the double-quote format, unless you're required to use a charlist—which is the case, for instance, when you're using Erlang libraries. You can use the following functions to convert between the two formats:
iex> String.to_charlist("converting to charlist")
'converting to charlist'
iex> List.to_string('converting to string')
"converting to string"
We'll now succinctly describe some other types. We'll begin with the types that build upon some types we've already described: keyword lists, ranges, mapsets, and IO lists.
Ranges, again, similar to what happens in Ruby, represent an interval between two integers. To create a range, we use this:
iex> 17..21
17..21
iex> 19 in 17..21
true
Similar to what we do with a list, we can use the in operator to check whether a number is between the start and the end of a range.
Despite not being mentioned in the data types section, functions in Elixir are a type as well–in fact, they are a first-class citizen, as they can be assigned to a variable and passed as arguments to other functions.
As with most functional programming languages, functions are an important type, hence they justify having their own section, away from other built-in types.
We will start by exploring anonymous functions, followed by an explanation of modules and named functions, and then we'll end this section with a quick tour of module attributes and directives.
