Goals

I come from the land of PHP and JavaScript, with very little Ruby experience. I have seen quite a few posts around the interwebs introducing Elixir from a Rubyist’s perspective but I haven’t really seen any from a perspective I can relate to. The syntax of Elixir is kind of foreign to most people outside of Ruby-land, so I hope to help push people a little bit over the initial hump with some examples using PHP and JavaScript (ES2015) along with the Elixir examples.

My goal with this post is to help introduce the syntax as well as a shallow introduction to immutability and how we use recursion instead of loops.

Syntax and Semantics

So, we have to admit that the Elixir syntax is very Ruby-like although the concepts and semantics are largely inherited from Erlang. However, Elixir introduces a lot of great new things of it’s own, as well as cherry-picking some nice concepts from other functional languages.

Types

Basic types

Please read the elixir guide on basic types for more detailed info, this is a good place to start.

These common types you should already be used to:

Boolean : true , false

: , Integer : 1

: Float : 3.14

: String : "Hello World" - always using double quotes.

: - always using double quotes. List: [1, 2, 3] - This is a linked list. Like an array in PHP or JavaScript, but not.

And maybe some you aren’t used to:

Atom : :foo - A constant whose value is its name. Might seem odd at first, but is pretty slick.

: - A constant whose value is its name. Might seem odd at first, but is pretty slick. Tuple: {1, "two", :three} - An ordered group of related values (any values) - Learn about tuples

Keyword Lists, Maps, and Structs

Please read the elixir guides on keyword lists & maps, and structs for more detailed info.

Keyword List : [one: 1, two: 2, three: 3] - This is similar to an associative array in PHP, or a plain object in JavaScript (except it is ordered).

: - This is similar to an associative array in PHP, or a plain object in JavaScript (except it is ordered). Map : %{name: "Joe", email: "joe@example.com"} - This is also similar to an associative array in PHP (except it is unordered) or a plain JavaScript object.

: - This is also similar to an associative array in PHP (except it is unordered) or a plain JavaScript object. Struct: %Person{name: "Joe", email: "joe@example.com"} - It’s like a map but with predefined properties.

Functions

In Elixir, functions (named functions) are always defined in modules. Modules are basically just however you decide that you want to group your functions. You should do this in a way that has meaning to you and makes sense in the context of your project. Defining a function is similar to any other language.

You define and use a function as follows: (I’m encapsulating the PHP and JavaScript versions in a class and object, to better compare with the Elixir module syntax)

PHP

class StrHelper { public static function uppity($str = "default") { return strtoupper($str); } } StrHelper::uppity("foo"); // "FOO"

JavaScript

let strHelper = { uppity(str = "default") { return str.toUpperCase() } } strHelper.uppity("foo") // "FOO"

Elixir

defmodule StrHelper do def uppity(str \\ "default") do String.upcase(str) end end StrHelper.uppity("foo") # "FOO"

Anonymous functions

Anonymous functions are first class values and can be passed as arguments or returned from other functions.

Here is how you define and call an anonymous function:

PHP

$uppity = function($bar) { return strtoupper($bar); } $uppity("bar"); // "BAR"

JavaScript

let uppity = function(bar) { return bar.toUpperCase() } uppity("bar") // "BAR"

Elixir

uppity = fn (bar) -> String.upcase(bar) end uppity.("bar") # "BAR"

Since these functions are only calling another function on the argument, there are better ways these could be written, but for the sake of this example I wanted to show the function yet keep the function body simple.

Implicit returns

You may have noticed that there is no return statement, because Elixir has implicit returns. This means that the result of the last statement is what is returned.

so:

defmodule Foo do def baz?(str) do if String.equivalent?(str, "baz") do "It's baz!" else "It's NOT baz!" end end end Foo.baz?("baz") # "It's baz!" Foo.baz?("bar") # "It's NOT baz!"

Passing "baz" above means the last statement executed is "It's baz!" .

Also, notice the function name baz? includes a ? character. This is a common convention in Elixir where a function that returns a boolean will include a ? in the name, as opposed to other languages where it would be common to name it something like is_baz .

Pattern matching

There is a lot to say about pattern matching, because it’s kind of a big deal in Elixir. However, I’m going to keep it shallow.

Pattern matching in function arguments

The last example could be re-written as:

defmodule Foo do def baz?("baz") do "It's baz!" end def baz?(str) do "It's NOT baz!" end end Foo.baz?("baz") # "It's baz!" Foo.baz?("bar") # "It's NOT baz!"

You can define the same function multiple times, and Elixir will execute the first one whose argument pattern matches. So, in the example above, the first function will only ever match when "baz" is passed, and the second one will match any other argument.

Pattern matching variable assignment

a = 1

This looks like typical assignment as done in other languages, but in Elixir it is actually matching the pattern of the left-hand side and right-hand side and doing the assignments based on that. Here is an example that might help clarify what this means. Pay attention to how the left-hand and right-hand side match, and how assignment is done based off of that:

{a, b, c} = {1, 2, 3} # a = 1, b = 2, c = 3 {i, j, 3} = {1, 2, 3} # i = 1, j = 2 {x, y, 9} = {1, 2, 3} # ** (MatchError) no match of right hand side value: {1, 2, 3}

Some more examples, note that assignment can only be done if the variable you want to assign is on the left:

x = 1 # 1 1 = x # 1 2 = x # ** (MatchError) no match of right hand side value: 1 2 = z # warning: variable "z" does not exist and is being expanded to "z()", # please use parentheses to remove the ambiguity or change the variable name z = 2 # 2

Collections

Elixir doesn’t have loops, so iterating collections is done with recursion.

Recursion

In a recursive function, we will call the same function recursively until a condition is met. Below, we will call the list recursively until the tail is empty, and return the empty list (see the first function definition), which will stop the recursion. This function is doing the same as our others, transforming a string to uppercase, except it is doing it for every item in a list.

defmodule Collection do def uppity([]), do: [] def uppity([head | tail]) do [String.upcase(head) | uppity(tail)] end end Collection.uppity(["foo", "bar", "baz"]) # ["FOO", "BAR", "BAZ"]

Note the multiple definitions of the same function, again. The first one that matches will be called.

You will see another (probably) new-to-you concept here. We can define a function without the do/end if we use , do: . This is good for short, single line functions.

Built-in collection functions

Luckily, we have functions that should cover at least all of your basic needs in modules like Enum , and List .

You should be familiar with a lot of the functions, especially the most common ones like map , filter , and reduce from your other languages. They are common in languages like PHP and JavaScript.

The most notable thing about these functions is that none of them will mutate the original collection. (Actually, no functions in elixir will, ever.)

Using a built-in map function to apply an anonymous function to each item in a collection:

JavaScript

let items = ["foo", "bar", "baz"] items.map((item) => { return item.toUpperCase() }) // ["FOO", "BAR", "BAZ"]

PHP

$items = ["foo", "bar", "baz"]; array_map(function($item) { return strtoupper($item); }, $items); // ["FOO", "BAR", "BAZ"]

Elixir

items = ["foo", "bar", "baz"] Enum.map(items, fn (item) -> String.upcase(item) end) # ["FOO", "BAR", "BAZ"]

Comprehensions

Comprehensions might have a similar feel to for loops (or for..in, or foreach) but they are not quite the same thing. Due to recursion and scope, for example, you can not iterate on a counter variable like you might be used to in non-functional languages.

items = ["foo", "bar", "baz"] i = 0 for item <- items do i = i + 1 IO.puts i end

outputs:

1 1 1

A comprehension will return a list of the results of each execution. I won’t really go further into this subject, however, since this is just an intro post. They have their uses, but I find myself rarely using comprehensions. However, there are cases where they are the most clear and concise way, so don’t underestimate them either.

Pipes

Pipes |> might look odd at first, but they are actually very simple and very awesome.

All that you really need to know is that they take the result of one function and pass it as the first argument into the next function. If you are familiar with unix, then you should understand the idea of a pipe operator, it’s the same idea.

For example, you could refactor this:

String.upcase("foo")

into:

"foo" |> String.upcase()

(don’t actually do this for a single function)

The real beauty comes from the fact that you can chain pipes as much as you want. This gets rid of temporary variables and also improves readability significantly.

Here is another example refactor where I will add some chained pipes. Let’s take this:

def add_space_and_upcase(str) do split = String.codepoints(str) spaced = Enum.join(split, " ") String.upcase(spaced) end add_space_and_upcase("foobar") # "F O O B A R"

and make it much better:

def add_space_and_upcase(str) do str |> String.codepoints() |> Enum.join(" ") |> String.upcase() end add_space_and_upcase("foobar") # "F O O B A R"

I think it’s clear to see how great this is.

Conclusion

Elixir clearly has some really great things going on, and we’re not even scratching the surface here, as I’ve mentioned nothing about processes or OTP.

I hope however, that this has helped you to at least not have a mini-wtf-heartattack when you look at some elixir code and gets you interested to learn more and try it out.

If you think I should include anything else, or have any critiques, please feel free to comment below.