These notes come as breadcrumbs left along my journey through the Complete Elixir and Phoenix Bootcamp. This is the first part that covers discovering Elixir. The second part will cover Phoenix. Here we go:
There are no methods in Elixir—just functions:
There are no objects in Elixir (nor methods). If they are about structs, then they should be referred to as structs (or generally as data) (and use "functions" instead of "methods").
–José Valim
-
- create a list based on existing lists
- distinct from the use of map and filter functions
We played with Lists, let's learn about Tuples:
- "like an array, where each index has a very special meaning"
- the ordering / contract is in the developer's head!
- can be seen as a key/value pair, where the key is the index
Pattern Matching
- "Elixir's replacement for variable assignment"
- Elixir, as a language governed by its design
=
starts the pattern matching sequence- create a mirror structure on the left hand side
- matches the data structure
- matches the number of values / elements
- enables you to avoid writing
if
statements- pattern matching in
case
statements
- pattern matching in
- pattern matching can also be done directly in the argument list
The Elixir Ecosystem
- Code we write »
- fed into » Elixir »
- transpiled into » Erlang »
- compiled and executed on » BEAM
- leveraging underlying Erlang modules:
:erlang.term_to_binary/1
:erlang.binary_to_term/1
:file.format_error/1
—thanks @pragdave:egd
—the erlang graphical drawer
- Code we write »
Atoms
- personally, I prefer the definition from Programming Elixir 1.3
Atoms are constants that represent something’s name. […]
An atom’s name is its value. Two atoms with the same name will always compare as being equal, even if they were created by different applications on two computers separated by an ocean.Unused variables
- Elixir provides a friendly warning at compile time
- Solution: underscore them:
_
or_reason
The Pipe operator
- the key is to write functions that take consistent first arguments
- the return of the previous function gets applied as the first argument in the subsequent function
- Joe Armstrong's explanation of the pipe operator made the most sense for me:
This is the recessive monadic gene of Prolog. The gene was dominant in Prolog, recessive in Erlang (son-of-prolog) but re-emerged in Elixir (son-of-son-of-prolog).
x |> y*
means callx
then take the output ofx
and add it as an extra argument toy
in the first argument position.So
x(1,2) |> y(a,b,c)
Means
newvar = x(1,2); y(newvar,a,b,c);
- the key is to write functions that take consistent first arguments
Documentation
- ExDoc
@moduledoc
for module documentation@doc
for function documentationmix docs
to generate
- ExDoc
Testing
- first-class citizen: fully featured out of the box
mix test
- Doctests
- write docs and tests at the same time
- examples in documentation stay up to date
- Scenario: explain to another engineer the simplest case of using a given function
- Additional assertions are possible, but you generally want to make only one assertion about the very last line: this is a documentation test about the
contains?/2
function, and not aboutcreate_deck/0
. So in practice we want to have just one, very small, very targeted assertion about the given function. - Ran by the
doctest Cards
line inside out test file - parse the module
- run any examples as an actual test
- Case Tests
- What behaviour do we want to test?
assert
versusrefute
- Elixir's functional programming style makes it so easy to test
- create basic object, representing our working data
- pass it off to the function we're testing
- do some basic checks on the returned object
Maps
key-value stores, similar to Ruby hashes, or Javascript objects
colours = %{primary: "red", secondary: "blue"}
accessing properties
- dot notation
- pattern matching
updating values
- immutable data: to update means to create a new data structure with the modifications
- two ways of achieving this:
- with a function:
Map.put/3
, etc leveraging the built in syntax —similar to Elm
%{colours | primary: "green"}
- only works for existing keys:
the VM is aware that no new keys will be added to the struct, allowing the maps underneath to share their structure in memory.
Keyword Lists
List
andTuple
merged into one- lists: like arrays, can be used for an arbitrary number of elements
- tuples: like arrays, where each index has a special meaning to us
- versus
Map
: there can be duplicate keys a list that contains tuples
colours = [{:primary, "red"}, {:secondary, "blue"}] # or colours = [primary: "red", secondary: "blue"]
Identicon Image Manipulation
- describing the business logic
- one root object / piece of data
- gets passed around through a Main Pipeline
- a series of small functions, that transform the data
- describing the business logic
Data Modeling
Struct
- it's just a
Map
plus - compile-time checks
- default values
- Looks a lot like a
Map
; why would we use aStruct
over aMap
? - a
Struct
enforces that the only properties that can be stored are the ones defined in the module - a normal
Map
is happy to let you insert any different property that you'd like - Why not add functions (methods / properties / instance methods) to structs? From an FP perspective
- it's a Map under the hood
- has no ability to attach any functions to it
- it can only hold some primitive data
- A single location to store all the data inside of our app
- The update syntax works best, as properties on a
Struct
are already defined
- it's just a
List
- Acessing the first X values from an arbitrarily long list
- the head and tail split pattern
[h | t]
- extended to
[a, b, c | _tail]
Tuple
- use a
Tuple
instead of aList
when the index has particular semantic
- use a
First-class functions
- in Elixir, referring to a function—
mirror_row
—it will call it by default - to pass a reference to a function, we use a special syntax
- Elixir provides guidance even through its compile error
[…] invalid args for
&
, expected an expression in the format of&Mod.fun/arity
,&local/arity
or a capture containing at least one argument as&1
- in Elixir, referring to a function—
-
- we can't mix clauses that expect a different number of arguments. A function always has a fixed arity.
- anonymous functions are closures, similar to lambdas in Ruby
A working Identicon program
- the pipe operator
- working with Erlang (
egd
) Enum
- anonymous functions
The second part will cover Phoenix. Back to the bootcamp now!