brew install elixir
Interactive Elixir
iex
Note: Hit ctrl-c twice to exit
http://getawesomeness.herokuapp.com/get/elixir
- Variables are immutable
- Not OO, data is passed in arguments
- Actor model for concurrent tasks
elixirc code.ex
File extensions (see below in Mix command)
.ex
files - generate production artifacts (.beam
files). e.g. lib files.exs
files - dont generate production artifacts. Used for scripting, e.g. config or testing
Run in iex
shell
iex -S mix
In code:
require IEx;
IEx.pry
- https://elixir-lang.org/getting-started/debugging.html
- https://elixirschool.com/en/lessons/specifics/debugging/
- http://blog.plataformatec.com.br/2016/04/debugging-techniques-in-elixir-lang/
IO.puts "Hello world"
IO.inspect(x)
Prefix :
Syntax
:module.function_name
Example:
:random.uniform()
# 0.7230402056221108
:erlang.float_to_binary(7.12, [decimals: 2])
# "7.12"
0xA
0b1
:hello
"""
Hello
World
"""
is_map(arg)
is_list(arg)
etc
In the REPL
message = "hello"
is_bitstring(message) # true
byte_size(message) # 5
Stored as linked lists Immutable: Lists cannot be mutated, new values are always created. Good for concurrency
[1, "Hello", :an_atom, true]
IO.puts([104, 101, 108, 108, 111]) # 'hello' charset
is_list('Hello') # true
length([1, 2, :true, "str"]) # 4
Concatenation
[1, 2, 3] ++ [4, 5, 6] # [1, 2, 3, 4, 5, 6]
Subtraction
[1, true, 2, false, 3, true] -- [true, false] # [1, 2, 3, true]
^ subtracts the first encountered value
Head & tail of list
list = [1, 2, 3]
hd(list) # 1
tl(list) # [2, 3]
Other functions
# list = [1, 2, 3]
List.delete(list, 2) # [1, 3]
List.delete_at(list, 2) # [1, 2]
List.first(list) # 1
List.flatten([1, 2, 3, [4, 5]]) # [1, 2, 3, 4, 5]
List.last(list) # 3
Iteration
def create_deck do
values = ["Ace", "Two", "Three", "Four", "Five", ...]
suits = ["Spades", "Clubs", "Hearts", "Diamonds"]
for value <- values, suit <- suits do
"#{value} of #{suit}"
end
end
Lists grow at the head
x = [1, 2, 3] # [1, 2, 3]
[0 | x ] # [0, 1, 2, 3]
^ Prepend to the head of the list and then reverse
Enum.reverse([0 | x]) # [3, 2, 1, 0]
Storage in memory:
- Stored as cons cells
[left | right]
- Stored in a tree structure - so it's fast to prepend, but slow to append elements e.g.
[4 | list ]
What: Ordered collection of elements
Can hold different types Stored in contiguous memory block Accessing values are quick Updating / inserting elements is slow
tuple = { 1, "Hello", :an_atom, true }
elem(tuple, 0) # 1
tuple_size({:ok, "hello"}) # 2
Most of the time, use a Tuple using 2 values Where the first value is an atom
e.g.
{:ok, "some content"}
usage:
{status, content} = {:ok, "content"}
{error, message} = {:error, "some error"}
Appending
tuple = {:ok, "Hello"} # {:ok, "Hello"}
Tuple.append(tuple, :world) # {:ok, "Hello", :world}
Inserting
tuple = {:bar, :baz}
new_tuple_1 = Tuple.insert_at(tuple, 0, :foo) # {:foo, :bar, :baz}
new_tuple_2 = put_elem(tuple, 1, :foobar) # {:bar, :foobar}
Convention: Snake case
hello = "hello world"
Can lead with _ but usually for variables not used again
div: div(10, 2) #5
rem: rem(20, 3) #Remainder: 2
=== type equality
!== type inequality
and
true and "hello" # "hello"
true and false # false
true and 0 # 0
and, or, && and || are short circuit operators
languages = ["Elixir", "Javascript", "Ruby"]
[first, second, third] = languages
OR
[head | tail] = languages # head = "Elixir", tail = ["Javascript", "Ruby"]
OR
[head | _ ] = languages # the rest of the list is ignored
[var_1, _unused_var, var_2] = [{"First variable"}, 25, "Second variable" ]
[_, [_, {a}]] = ["Random string", [:an_atom, {24}]]
# ["Random string", [:an_atom, {24}]]
The other values will be ignored if there is a _
Matching left and right values
a = 25
b = 25
^a = b
Example using concatenation <>
(and pattern matching)
"John " <> last_name = "John Smith"
IO.puts last_name #Smith
Example on function arguments (avoid if statements) In this case - avoid passing "type" argument to the function
defmodule Account do
def run_transaction(balance, amount, :deposit) do
balance + amount
end
def run_transaction(balance, amount, :withdrawal) do
balance - amount
end
end
Usage
Account.run_transaction(1000, 50, :deposit)
This uses the cons
operator |
to split the list
defmodule Language do
def print_list([head | tail]) do
IO.puts "Head: #{head}"
IO.puts "Tail: #{tail}"
end
end
Language.print_list(["Elixir", "Javascript", "Ruby"])
No for loops, use recursion
e.g.
defmodule Language do
def print_list([head | tail]) do
IO.puts head
print_list(tail) # remaining elements are passed
end
def print_list([]) do
end
end
Another example with pattern matching:
defp find_tax([%{ "state" => "FL", "rate" => rate } | _ ]) do
rate
end
defp find_tax([_ | tail]) do
find_tax(tail)
end
defp find_tax([]) do
raise "FL rate not found"
end
if boolean-statement do
#Code to be executed if condition is satisfied
else
#Code to be executed if condition is not satisfied
end
unless boolean-statement do
#Code to be executed if condition is false
end
This works like if-else multiple conditionals
When: checking multiple conditions
guess = 46
cond do
guess == 10 -> IO.puts "You guessed 10!"
guess == 46 -> IO.puts "You guessed 46!"
guess == 42 -> IO.puts "You guessed 42!"
true -> IO.puts "I give up."
end
Another example
def valid_transfer?(amount, hourOfDay) do
cond do
hourOfDay < 12 -> amount <= 5000
hourOfDay < 18 -> amount <= 1000
true -> amount <= 300
end
end
^ The true statement is last, as cond
will raise an error if no conditions are met
What: tests a value against a set of patterns
When: matching
on multiple patterns (e.g. match on return values)
Like switch statement If pattern matches, then exit. Otherwise throw CaseClauseError
case 3 do
1 -> IO.puts("Hi, I'm one")
2 -> IO.puts("Hi, I'm two")
3 -> IO.puts("Hi, I'm three")
_ -> IO.puts("Oops, you dont match!")
end
Guard Clauses
Uses when
defmodule Account do
def list_transactions(filename) do
case File.read(filename) do
{ :ok, content }
when byte_size(content) > 10 -> "Content: #{content}"
{ :error, type } -> "Error: #{type}"
end
end
end
Nested-if statements should be refactored to use case
e.g. Refactor this
defmodule Account do
def list_transactions(filename) do
if result == :ok do
"Content: #{content}"
else
if result == :error do
"Error: #{content}"
end
end
end
end
To:
defmodule Account do
def list_transactions(filename) do
case File.read(filename) do
{ :ok, content } -> "Content: #{content}"
{ :error, type } -> "Error: #{type}"
end
end
end
WIP
String length
String.length("Hello")
Interpolation/Substitution
"X-men #{x}"
String Concatenation
"Hello" <> " " <> "World"
Reversal
String.reverse("Elixir")
String comparison
== or the === operators
String Matching
Output: true / false
IO.puts(String.match?("foo", ~r/foo/))
IO.puts("foo" =~ ~r/foo/)
Conversion
Integer.parse("1") # {1, ""}
String.to_integer("1") # 1
Common methods
String.at("Hello", 1) # "e"
String.capitalize("hi") # "Hi"
String.contains?("Hello", "H") # true
String.downcase("HI") # "hi"
String.ends_with?("hello", "lo") # true
String.first("hello") # "h"
String.last("hello") # "o"
String.replace("hello", "l", "p") # "heppo"
String.slice("hello", 0, 2) # "he"
String.slice("hello", -2, 2) # "lo"
String.split("hello", "") # ["h", "e", "l", "l", "o", ""]
String.upcase("hello") # "HELLO"
http://elixir-lang.github.io/getting-started/binaries-strings-and-char-lists.html
UTF-8 is used as an encoding
Special chars like ł
need a certain amount of bytes to represent the char (code point is 322
)
byte_size("hełło") # 7
String.length("hełło") # 11
is_binary("hełło") # true
To get the codepoint:
?a # 97
?ł #322
String.codepoints("hełło") # ["h", "e", "ł", "ł", "o"]
A binary is a sequence of bytes enclosed in <<>>
<<0, 1, 2, 3>> # <<0, 1, 2, 3>>
byte_size(<<0, 1, 2, 3>>) # 4
To test if the binary makes a valid String:
String.valid?(<<239, 191, 19>>) # false
String.valid?(<<322, 97>>) # true. The value is ał
String Concatenation
<<0, 1>> <> <<2, 3>> # <<0, 1, 2, 3>>
"hełło" <> <<0>> # <<104, 101, 197, 130, 197, 130, 111, 0>>
Each binary only goes up to 255
<<255>> # <<255>>
<<256>> # truncated <<0>>
Specify number of bits to store the number
<<256 :: size(16)>> # use 16 bits (2 bytes) to store the number <<1, 0>>
<<256 :: utf8>> # the number is a code point "Ā"
<<256 :: utf8, 0>> # Store the overflow when binary is > 255: <<196, 128, 0>>
Passing in 1 bit
is_binary(<< 1 :: size(16)>>) # true is_binary(<< 1 :: size(32)>>) # true is_binary(<< 1 :: size(64)>>) # true is_binary(<< 1 :: size(128)>>) # true is_binary(<<1 :: size(15)>>) # false
Pattern match binaries/bitstrings
<<0, 1, x>> = <<0, 1, 2>> # <<0, 1, 2>>
"he" <> rest = "hello" # "hello"
rest # "llo"
Charlists are used mostly when interfacing with Erlang, in particular old libraries that do not accept binaries as arguments
'hełło' # [104, 101, 322, 322, 111]
is_list 'hełło' # true
is_list 'hi' # false
List.first('hello') # 104
Converting between charlist and string
- These functions are polymorphic (convert different types to strings)
to_charlist "hełło" # [104, 101, 322, 322, 111]
to_string 'hełło' # "hełło"
to_string :hello # "hello"
to_string 1 # "1"
3 uses:
- They serve to annotate the module, often with information to be used by the user or the VM.
- They work as constants.
- They work as a temporary module storage to be used during compilation.
As constants:
defmodule MyServer do
@initial_state %{host: "127.0.0.1", port: 3456}
IO.inspect @initial_state
end
range = 1..5 #1..5
What:
List of two-value tuples
Usually used as options argument in a function
Features:
- Keys must be atoms.
- Keys are ordered, as specified by the developer.
- Keys can be given more than once.
Syntax
[key: value]
E.g.
list = [{:a, 1}, {:b, 2}]
list == [a: 1, b: 2]
In a function:
defmodule Account do
def balance(transactions, options) do
currency = options[:currency]
symbol = options[:symbol]
balance = calculate_balance(transactions)
"Balance in #{currency}: #{symbol}#{balance}"
end
end
Account.balance(transactions, currency: "pounds", symbol: "£")
Other functions:
list ++ [c: 3] # [a: 1, b: 2, c: 3]
new_list = [a: 0] ++ list # [a: 0, a: 1, b: 2]
new_list[:a] # 0 front values are fetched on lookup
Keyword.get(new_list, :b) # 2
Keyword.get_values(new_list, :a) # [0, 1]
Keyword.values(new_list) # [0, 1, 2]
new_list = Keyword.put_new(new_list, :c, 5) # [c: 5, a: 0, a: 1, b: 2]
Keyword.delete_first(new_list, :a) # [c: 5, a: 1, b: 2]
Keyword.delete(new_list, :a) # [c: 5, b: 2] - this would delete all instances of keys with :a
Keyword.get(new_list, :a) # nil
if/2
macro
if false, do: :this, else: :that # :that
if(false, [do: :this, else: :that]) # :that
if(false, [{:do, :this}, {:else, :that}]) # :that
Pattern Matching on keyword lists
[a: x, b: y] = [a: 1, b: 2] # [a: 1, b: 2]
x # 1
y # 2
Not usually done as this requires matching items and order e.g.
[a: a] = [a: 1, b: 2] # MatchError
[b: b, a: a] = [a: 1, b: 2] # MatchError
Equality
list_1 = [{:a, 1}, {:b, 2}]
list_2 = [a: 1, b: 2]
list_1 == list_2 # true
When: Want a collection of key-value pairs, simple efficient storage
Denoted by: %{}
Features
- Maps allow any value as a key.
- Maps' keys do not follow any ordering.
- Pattern matching works well
- All keys in a map are atoms
Docs: https://hexdocs.pm/elixir/Map.html
Example:
person = %{ "name" => "Brooke", "age" => 42 }
Map.fetch(person, "name")
Another example:
map = %{:a => 1, 2 => :b} # %{2 => :b, :a => 1}
Retrieval
map[:a] # 1
map[:c] # nil
map.a # 1
map[:b] # nil
map[2] # :b
Using Fetch
Map.fetch(map, 2) # {:ok, :b}
Map.fetch(map, :d) # :error
Map.keys(map) # [2, :a]
Map.values(map) # [:b, 1]
Map.fetch(:c) # nil
Map.fetch!(map, :a) # 1, fetches only the value
Map.fetch!(map, :c) # KeyError
Shorthand Syntax
%{"hello" => "world", a: 1, b: 2} # %{:a => 1, :b => 2, "hello" => "world"}
Nested Structures
users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
Pattern matching:
- Matched on the first subset of a value
person = %{ "name" => "Brooke", "age" => 42 }
%{ "name" => name, "age" => age } = person
Equivalent to:
%{ "name" => name, "age" => age } = %{ "name" => "Brooke", "age" => 42 }
Can Pattern match on portions
NOTE: Only Maps support partial match
person = %{ "name" => "Brooke", "age" => 42 }
${ "name" => name } = person
Nested example:
person = %{ "name" => "Sam", "age" => 31,
"bank_info" => %{ "routing" => "001002", "account" => "123123" }
}
%{"bank_info" => %{"account" => account}} = person
IO.puts "Bank Account is #{account}"
Other uses:
%{} = %{:a => 1, 2 => :b} # %{2 => :b, :a => 1}
%{:a => a} = %{:a => 1, 2 => :b} # %{2 => :b, :a => 1} => a is assigned
%{:a => 1, 2 => :b } = %{:a => 1, 2 => :b} # %{2 => :b, :a => 1}
%{2 => :b} = %{:a => 1, 2 => :b} # %{2 => :b, :a => 1}
%{:c => c} = %{:a => 1, 2 => :b} # match error
Adding
Use: Map.put
or:
mm = %{a: 1, b:2}
iex> %{mm | a: 0 } # %{a: 0, b: 2}
iex> mm # %{a: 1, b: 2}
Equality
Map.equal?(%{:a => 1}, %{:a => 1}) # true
defmodule
- macro to create modules
defmodule Math do
def sum(a, b) do
a + b
end
end
Math.sum(1, 2) # 3
Module Nesting
defmodule Foo do
defmodule Bar do
end
end
Foo.Bar
Foo.Bar.Baz # still valid
You can define arbitrarily nested modules without defining any module in the chain
Extension: .exs
Execute: elixir math.exs
During execution - the file is compiled and load modules into memory
Functions can be
- assigned to variables
- passed as arguments to other functions
Defined with arity e.g. more than one function with same name but can have different number of params
Anonymous functions
- no name
- no module
Creation:
fn ->
Invoking:
function_name.(args)
e.g.
max_balance = fn(amount) -> "Max: #{amount}" end # Function<6.99386804/1 in :erl_eval.expr/5>
max_balance.(500) # "Max: 500"
Shorthand:
max_balance = &("Max: #{&1}")
max_balance.(500)
the function transaction
is passed
defmodule Account do
def run_transaction(balance, amount, transaction) do
if balance <= 0 do
"Cannot perform any transaction"
else
transaction.(balance, amount)
end
end
end
account_transaction = fn
(balance, amount, :deposit) -> balance + amount
(balance, amount, :withdrawal) -> balance - amount
end
account_transaction.(100, 40, :deposit)
account_transaction.(100, 40, :withdrawal)
deposit = fn(balance, amount) -> balance + amound end
Same as
deposit = &(&1 + &2) # &1 is balance, &2 is amount
Inline:
Account.run_transaction(1000, 20, &(&1 + & 2))
Use \\
for optional args
Use ||
to set default value
E.g.
defmodule Account do
def balance(transactions, options \\ []) do
currency = options[:currency] || "dollar"
symbol = options[:symbol] || "$"
end
end
Account.balance(transactions)
defmodule ModuleName do
defp function_name do
...
end
end
^ Never called outside of it's module
Definition:
defmodule User do
defstruct name: "John", age: 27
end
Creation:
new_john = %User{} # %User{age: 27, name: "John"}
ayush = %User{name: "Ayush", age: 20} # %User{age: 20, name: "Ayush"}
megan = %User{name: "Megan"} # %User{age: 27, name: "Megan"}
Access:
john.name # John
john.age # 27
Update:
meg = %{john | name: "Meg"} # %User{age: 27, name: "Meg"}
Note: Similar to Records
When: you want more structured data
Must be within a Module
defmodule MyModule do
require Record
Record.defrecord :user, name: "john", age: 25
end
Importing a record
import User
Creation
record = user() #=> {:user, "meg", 25}
record = user(age: 26) #=> {:user, "meg", 26}
Get a field
user(record, :name) #=> "meg"
Update record
user(record, age: 26) #=> {:user, "meg", 26}
To get the zero-based index of the field in record tuple (index 0 is occupied by the record "tag")
user(:name) #=> 1
Convert a record to a keyword list
user(record) #=> [name: "meg", age: 26]
TODO
TODO
Ranges are streams
range = 1..5 # 1..5
Enum.map range, &(&1 * 2) # [2, 4, 6, 8, 10]
Enum.map range, &(&1 + 2) # [3, 4, 5, 6, 7]
Streams map to a range
range = 1..3 # 1..3
stream = Stream.map(range, &(&1 * 2)) # Stream<[enum: 1..3, funs: [#Function<46.40091930/1 in Stream.map/2>]]>
Enum.map(stream, &(&1 + 1)) #[3, 5, 7]
[3, 5, 7] #[3, 5, 7]
Stream to List
Enum.to_list(stream)
Other uses
Enum.map(["a", "b", "c"], &(&1)) # ["a", "b", "c"]
Enum.map(["a", "b", "c"], &(&1 <> "x" )) # ["ax", "bx", "cx"]
Enum.map(["1", "2", "3"], &(String.to_integer(&1))) # [1, 2, 3]
stream = Stream.chunk_by([1, 2, 2, 3, 4, 4, 6, 7, 7], &(rem(&1, 2) == 1))
Enum.to_list(stream) # [[1], [2, 2], [3], [4, 4, 6], [7, 7]]
Functions that return Streams
E.g.
IO.stream/2 - streams input lines, one by one
URI.query_decoder/1 - decodes a query string, pair by pair
Provides functions to iterate over Enumerables
Enum.at([1,2,3], 1) # 2
Enum.each([1,2,3,4,5], fn(x) -> IO.puts x end) # 1 2 3 4 5
Enum.empty?([]) # true
Enum.fetch([2, 4, 6], 0) # {:ok, 2}
Enum.count([1,213,1,34,567,1,9], &(&1 == 1)) # 3
TODO
Used for meta-programming Expanded at compile time
https://elixir-examples.github.io/examples/alias-use-import-require
alias, required, import
- directives (lexically scoped)
Usage:
alias Foo.Bar, as: Bar # Foo.Bar
Bar # Foo.Bar
same as
alias Foo.Bar # Foo.Bar
require Foo
import Foo
use Foo # extension point
Examples
String == is Elixir.String == :"Elixir.String"
defmodule Stats do
alias Math.List, as: List # Note: normal List needs to be accessed by Elixir.List
end
Shorthand:
alias Math.List
alias Math.List, as: List
List # Math.List
Lexical Scope:
- The alias can be used inside functions
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
end
Calling Erlang Modules
:lists.flatten([1, [2], 3]) # [1, 2, 3]
If you need to use a macro, then need to require
the module
e.g.
require Integer
Integer.is_odd(3)
When: Accessing functions/macros from other modules without using full qualitifed name
E.g.
import List, only: [duplicate: 2] # only is optional
duplicate :ok, 3 # [:ok, :ok, :ok]
import List, except: [first: 1] # exclude first function/macro
import Integer, only: :macros
import Integer, only: :functions
Lexical Scope
defmodule Math do
def some_function do
import List, only: [duplicate: 2]
duplicate(:ok, 10)
end
end
When: Bringing external functionality into current lexical scope e.g. modules
e.g.
defmodule AssertionTest do
use ExUnit.Case, async: true
test "always pass" do
assert true
end
end
Behind the scenes:
defmodule Example do
use Feature, option: :value
end
same as
defmodule Example do
require Feature
Feature.__using__(option: :value)
end
alias MyApp.{Foo, Bar, Baz}
Same as
alias MyApp.Foo
alias MyApp.Bar
alias MyApp.Baz
Reading
{:ok, contents} = File.read("test.txt")
IO.puts(contents) # "hello\n"
Contents only (or error)
contents = File.read!("test.txt") # "hello\n"
Don't know if success/failure, a way to handle this
def Account do
def parse_file({:ok, content}) do
IO.puts "#{content}"
end
def parse_file({:error, error}) do
IO.puts "Error parsing file"
end
end
File.read("transactions.csv") |> Account.parse_file()
Can also read files in chunks e.g. large files
File.open! file, fn(pid) ->
IO.read(pid, 25) == "some text to compare to" #reads first 25 chars
end
mix new project-name
Kind of like rake
e.g.
> mix new expenses
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/expenses.ex
* creating test
* creating test/test_helper.exs
* creating test/expenses_test.exs
Complicated, nested functions => use Pipe Operator
E.g.
defmodule Account do
def balance(initial, spending) do
interest(discount(initial, 10), 0.1)
end
def discount(total, amount) do
end
def interest(total, rate) do
end
end
This helps with readability
Example:
defmodule Account do
def balance(initial, spending) do
discount(initial, 10) |> interest(0.1)
end
...
end
The result of discount(initial, 10)
is piped to the interest
function i.e. interest(result, 0.1)
Note: each function call should go to a newline if the line is too long
@doc """
documentation goes here
"""
https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html
Check the version
mix --version
mix new project-name
Directory:
├── README.md
├── config
│ └── config.exs
├── lib
│ └── budget.ex
├── mix.exs
└── test
├── budget_test.exs
└── test_helper.exs
Compile the project
mix compile
e.g. project called budget
Mix will create a module called budget
under lib/budget.ex
defmodule Budget do
# self defined functions
end
By default:
defmodule Budget do
@moduledoc """
Documentation for Budget.
"""
@doc """
Hello world.
## Examples
iex> Budget.hello
:world
"""
def hello do
:world
end
end
mix run -e "code"
or
mix run -e "Module.function_name(params)"
^ Evaluate code in the context of the application
(Budget is the module with a function function_name
)
e.g.
mix run -e "Budget.current_balance(100, 20) |> IO.puts"
What happens:
- compile application
- load bytecode into Erlang VM
- detect
-e
option and evaluate the arg as code
Start the session that loads the project
iex -S mix
Call a function on a module
> Budget.hello # :world
budget
|
|-- README.MD
|
|-- mix.exs
|
|-- config
| |
| |-- config.exs
|
|-- lib
| |
| |-- budget.ex
|
|-- test
|
|-- budget_test.exs
|
|-- test_helper.exs
.exs
- scripts e.g. tests and config
.ex
- compiled code and production artifacts e.g. lib/
Budget.Conversion
Directory structure:
budget
|
|-- README.MD
|
|-- mix.exs
|
|-- config
| |
| |-- config.exs
|
|-- lib
| |
| |-- budget.ex
| |
| |-- conversion.ex
|
|-- test
mix help
Dependencies are declared in mix.exs
file in the project
Definition in:
defp deps
block- list of tuples
E.g.
defmodule Budget.Mixfile do
defp deps do
[{:httpoison, "~> 0.10.0"}, {:poison, "~> 3.0"}]
end
end
mix deps.get
Get installed to deps
dir
budget
|
|-- README.MD
|
|-- mix.exs
|
|-- config
|
|-- lib
|
|-- deps
|
|-- httpoison
|
|-- exjsx
Also updates the mix.lock
file
lib/mix/tasks/
e.g. lib/mix/tasks/list_transactions.ex
defmodule Mix.Tasks.ListTransactions do
use Mix.Task
@shortdoc "List transactions from CSV file"
def run(_) do
Budget.list_transactions |> IO.inspect
end
end
Add: default_task
to the /mix.exs
project file
defmodule Budget.Mixfile do
def project do
[
app: :budget,
version: "0.1.0",
elixir: "~> 1.5",
start_permanent: Mix.env == :prod,
deps: deps(), default_task: "list_transactions"
]
end
...
end
$mix compile
$mix
Compiling 2 files (.ex)
Generated budget app
"listing transaction"
To list everything
$mix help
mix # Runs the default task (current: "mix list_transactions")
mix app.start # Starts all registered apps
mix app.tree # Prints the application tree
mix archive # Lists installed archives
mix archive.build # Archives this project into a .ez file
mix archive.install # Installs an archive locally
mix archive.uninstall # Uninstalls archives
mix clean # Deletes generated application file
...
mix test
Tests
defmodule StreamersTest do
use ExUnit.Case, async: true
doctest Streamers
@index_file "test/fixtures/emberjs/9af0270acb795f9dcafb5c51b1907628.m3u8"
test "finds index file in a directory" do
assert Streamers.find_index("test/fixtures/emberjs") == @index_file
end
end
Notes:
async: true
- run tests in paralleldoctest Streamers
- documentation@index_file
- attribute, like a constant
iex --name [email protected]
iex --name [email protected]
iex([email protected])> Node.self
:"[email protected]"
iex([email protected])> Node.connect(:"[email protected]")
iex([email protected])> Node.list
[:"[email protected]"]
Official
- https://elixir-lang.org/docs.html
- https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html
- https://elixir-lang.org/crash-course.html
Others
- https://www.tutorialspoint.com/elixir/
- https://elixirschool.com/en/
- https://hexdocs.pm/elixir/Map.html
- http://elixir-recipes.github.io/
- https://github.com/cleverbunny/elixir-flashcards
- https://github.com/cleverbunny/elixir-flashcards/blob/master/Fundamentals%201.pdf
- https://github.com/sger/ElixirBooks
- https://gist.github.com/michalmuskala/ab8011adaf54dc33e8018e14a983378c
- https://ericdouglas.github.io/2017/08/27/elixir-built-in-documentation-and-testing-tools/
Pragmatic
- https://pragprog.com/book/elixir/programming-elixiraction
- https://www.manning.com/books/elixir-in-action
- https://pragmaticstudio.com/courses/elixir
List of libraries
CheatSheet
Blogs
- https://medium.com/@stueccles/what-i-learned-migrating-a-rails-app-to-elixir-phoenix-f707436749aa
- https://littlelines.com/blog/2014/07/08/elixir-vs-ruby-showdown-phoenix-vs-rails
- https://medium.com/@lasseebert/iterating-in-elixir-90fdc0005dfc
- http://rmosolgo.github.io/blog/2016/05/01/ruby-class-meet-elixir-module/
- https://medium.brianemory.com/elixir-lists-tuples-pipe-operators-data-structures-oh-my-9997f594ed29
- https://til.hashrocket.com/elixir
- https://joearms.github.io/published/2016-03-13-Calling-Elixir-From_Erlang.html
- http://codelever.com/blog/2015/12/21/working-with-erlang-records-in-elixir/
- https://app.pluralsight.com/player?course=meet-elixir&author=jose-valim&name=meet-elixir-m02-syntax&clip=2&mode=live
- https://ericdouglas.github.io/2017/08/27/elixir-built-in-documentation-and-testing-tools/
- https://code.tutsplus.com/articles/new-course-get-started-with-phoenix--cms-29474
- https://online.pragmaticstudio.com/tour/courses/elixir/steps/3
- Confident Elixir - https://www.youtube.com/watch?v=E-3G7g0Dm7c
Plural Sight
- https://www.pluralsight.com/courses/elixir-getting-started
- https://www.pluralsight.com/courses/meet-elixir
CodeSchool
- https://www.codeschool.com/learn/elixir
- https://www.codeschool.com/screencasts/build-a-bank-statement-cli-application-with-elixir
- https://www.codeschool.com/screencasts/build-a-bank-statement-cli-application-with-elixir
Udemy
- https://www.udemy.com/learn-elixir-beginner/
- https://www.udemy.com/elixir-for-beginners/
- https://www.udemy.com/the-complete-elixir-and-phoenix-bootcamp-and-tutorial/
Umbrella Apps
- https://8thlight.com/blog/georgina-mcfadyen/2017/05/01/elixir-umbrella-projects.html
- https://elixir-lang.org/getting-started/mix-otp/dependencies-and-umbrella-apps.html
- https://elixirschool.com/en/lessons/advanced/umbrella-projects/
- http://elixir-lang.readthedocs.io/en/latest/mix_otp/7.html
- https://elixirforum.com/t/adding-an-umbrella-to-deps/5123
- Georgina McFayden's talk at Elixir LDN 2017 - https://www.youtube.com/watch?v=jhZwQ1LTdUI
Feature Flags
Debugging
- https://github.com/codeschool/budget-elixir
- https://ericdouglas.github.io/2017/08/27/elixir-built-in-documentation-and-testing-tools/
Phoenix
Sugar
Dynamo (note: currently under mainteance mode)
-
ExDoc
-
Bureaucrat - https://github.com/api-hogs/bureaucrat - a tool that attempts to solve this discrepancy problem. Bureaucrat is a library that generates API documentation from tests
- credo - static analysis - https://github.com/rrrene/credo
- test coverage - https://github.com/parroty/excoveralls
- dialyxir - mix tasks for Dialyzer (static analysis for type consistency) - https://github.com/jeremyjh/dialyxir
Ecto
Issues
- DB Joiins - elixir-ecto/ecto#2389
ExUnit
Wallaby (concurrent browser testing)
Mocks
- http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-contracts/
- Mox - https://github.com/plataformatec/mox
- Terraform - move old APIs to new endpoint (reverse-proxy to legacy system until new app handles the request internally) - https://github.com/poteto/terraform
- Distillery - https://github.com/bitwalker/distillery