In this post we’re gonna see how to transform a simple script from Ruby to Elixir.
Installing Elixir
The first thing you need is to have Elixir installed on your box, the instructions are dead simple and you can
find them in the official Getting Started page.
For example on OS X is as simple as brew update; brew install elixir
.
The Ruby Script
The script is the one I use to fire up my editor adding support for the file:line:column
format that is often found in error stacktraces. I keep this script in ~/bin/e
.
#!/usr/bin/env ruby
command = ['mate']
if ARGV.first
file, line_and_column = ARGV.first.split(':', 2)
command << file
command += ['-l', line_and_column] if line_and_column
end
command << '.' if command.size == 1
exec *command
Take 1: Imperative Elixir
As we all know, no matter the language, you can keep your old style. In this first example we’ll see the same
#!/usr/bin/env elixir
if System.argv != [] do
[file| line_and_column] = String.split(hd(System.argv), ":", parts: 2)
args = [file]
if line_and_column != [] do
args = args ++ ["-l"| line_and_column]
end
else
args = ["."]
end
System.cmd("mate", args)
The “Guillotine” operator
The first thing we notice the change in syntax for the splat assignment:
# Ruby
a, b = [1,2,3]
a # => 1
b # => 2
<pre><code class="elixir"># Elixir
[a| b] = [1,2,3]
a # => 1
b # => [2,3]
The |
operator in Elixir will in fact take out the head of the list and leave the rest on its right.
It can be used multiple times:
[a| [b| c]] = [1,2,3]
a # => 1
b # => 2
c # => [3]
what happens here is that the list that in the first example was b
is now beheaded again.
If instead we wanted c
to equal 3
the assignment would look like this:
[a| [b| [c]]] = [1,2,3]
a # => 1
b # => 2
c # => 3
As we can see Elixir matches the form of the two sides of the assignments and extracts values and variables accordingly.
Other notes
Let’s see a couple of other things that we can learn in this simple example
List concatenation: ++
The ++
operator simply concatenates two lists:
a = [1,2] ++ [3,4]
a # => [1,2,3,4]
Double quoted "strings"
All strings need to be double quoted in Elixir, as single quotes are reserved for other uses.
I make the mistake of using single quotes all the time. Probably that’s the price for being a
ROFLScale expert.
Take 2: First steps in Pattern Matching
With this second version we’re gonna see the pattern matched case
.
Notice anything?
Yes. All if
s are gone.
#!/usr/bin/env elixir
args = System.argv
args = case args do
[] -> []
[""] -> []
[path] -> String.split(path, ":", parts: 2)
end
args = case args do
[] -> ["."]
[file] -> [file]
[file, ""] -> [file]
[file, line_and_column] -> [file, "-l", line_and_column]
end
System.cmd("mate", args)
We now reduced the whole program to a couple of switches that will route the input and transform it
towards the intended result.
That’s it. No highlights for this implementation. Just a LOLCAT.
Take 3: Modules and pipes
<pre><code class="elixir">#!/usr/bin/env elixir
defmodule Mate do
def open(argv), do: System.cmd(“mate”, argv |> parse_argv)
def parse_argv([]), do: [“.”]
def parse_argv([options]) do
[file| line_and_column] = String.split(options, “:”, parts: 2)
[file| line_and_column |> line_option]
end
def line_option([]), do: []
def line_option([“”]), do: []
def line_option([line_and_column]), do: [“-l”, line_and_column]
end
Mate.open System.argv
Module and defs
As you have seen we have now organized out code into a module and moved stuff to defined module
functions. The same function can be defined multiple times, Elixir will take care of matching the arguments
you pass to a function to the right.
Let’s review the two forms of function definition:
defmodule Greetings do
# extended
def hello(name) do
IO.inspect("hello #{name}")
end
# onliner
def hello(), do: IO.inspect("hello world!")
end
Greetings.hello "ppl" # => "hello ppl"
Greetings.hello # => "hello world!"
Be sure to remember the comma before do:
otherwise Elixir will complaint.
The |>
pipe operator
If you’re familiar with commandline piping you’ll fell like at home with the pipe operator.
Basically it will take the result of each expression and pass it as the first argument of the next one.
Let’s see an example:
"hello world" |> String.capitalize |> IO.inspect # => "Hello world"
That is just the same of:
s = "hello world"
s = String.capitalize(s)
s = IO.inspect(s)
s # => "Hello world"
or
IO.inspect(String.capitalize("hello world")) # => "Hello world"
Where the latter is probably the least comprehensible to human eyes
What’s next?
Playing
Studying
- Look out for my next article on Elixir by following our twitter account
- Read Programming Elixir book by the PragProgs