Created
June 25, 2016 13:28
-
-
Save cmalven/0446ce01a6cb27658d5e940f0a8b5be0 to your computer and use it in GitHub Desktop.
Programming Elixir Notes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Programming Elixir | |
## Value Types | |
# Integers | |
6 | |
1_000_000 | |
# Floating Point | |
1.0 | |
0.2456 | |
# Atoms | |
:fred | |
# Ranges | |
0..10 | |
# Regular Expressions | |
myReg = ~r{[aeiou]} | |
Regex.run myReg, "caterpillar" | |
Regex.scan myReg, "caterpillar" | |
Regex.split myReg, "caterpillar" | |
Regex.replace myReg, "caterpillar" | |
## Collection Types | |
# Tuples | |
{ 1, 2 } | |
{ :ok, 42, "next" } | |
# Lists | |
[1, 2, 3] | |
["a", "b", "c"] | |
# List operators | |
[1, 2, 3] ++ [4, 5, 6] # concatenation, yields [1, 2, 3, 4, 5, 6] | |
[1, 2, 3] -- [1, 3] # difference, yields [2] | |
1 in [1, 2, 3, 4] # membership, yields true | |
# Keyword Lists | |
[ name: "Dave", city: "Dallas", likes: "Programming" ] | |
# gets converted into a list of two-value tuples | |
[ {:name, "Dave"}, {:city, "Dallas"}, {:likes, "Programming"} ] | |
# Maps | |
states = %{ "AL" => "Alabama", "WI" => "Wisconsin" } | |
state = states["AL"] | |
# If the key is an atom, you can use the same shortcut that you use with keyword lists | |
colors = %{ red: 0xff0000, green: 0x00ff00, blue: 0x0000ff } | |
# and you can also do dot notation if key is atom | |
color = colors.green | |
## Operators | |
# Comparison | |
a === b # strict equality (so 1 === 1.0 is false) | |
a !== b # strict inequality (so 1 !== 1.0 is true) | |
a == b # value equality (so 1 == 1.0 is true) | |
a != b # value inequality (so 1 != 1.0 is false) | |
# Boolean | |
a or b # true if a is true, otherwise b | |
a and b # false if a is false, otherwise b | |
not a # false if a is true, true otherwise | |
# Relaxed Boolean | |
a || b # a if a is truthy, otherwise b | |
a && b # b if a is truthy, otherwise a | |
!a # false if a is truthy, otherwise true | |
# Arithmetic | |
4 / 2 # yields a floating point: 2.0 | |
div(4, 2) # yields an integer: 2 | |
rem(11, 3) # remainder, yields 2 | |
## Variable Scope | |
# with | |
thing = with foo = [1, 2, "dog"], | |
bar = "dog" | |
do | |
"Found is #{bar in foo}" | |
end | |
## Pattern Matching | |
# When = is used in Elixer, it isn't assignment, Elixier tries to make the value on the left side the same as the right | |
list = [1, 2, 3] | |
[a ,b, c] = list # a is now 1, b is 2, and so on | |
# Ignoring values with _ | |
[1, _, _] = [1, 2, 3] | |
[1, _two, _three] = [1, 2, 3] # you can also give them names if you want to ignore them but make it clear what they represent | |
# Pinning variables | |
a = 1 # returns 1 | |
a = 2 # returns 2 | |
^a = 1 # returns MatchError, because a is still 2 | |
[^a, 2, 3 ] = [ 1, 2, 3 ] # works in larger patterns too | |
## Anonymous Functions | |
times = fn (a, b) -> a * b end | |
times = fn a, b -> a * b end # parens for params can be ommitted | |
times.(1, 2) | |
# Patterning matching and functions | |
swap = fn { a, b } -> { b, a } end # pattern matching | |
# Functions with multiple bodies based on pattern of params | |
pets = fn | |
"dog" -> "Dogs are happy" | |
"cat" -> "Cats are lazy" | |
_ -> "Things are confusing" | |
end | |
# & Helper Notation | |
add_one = &(&1 + 1) # same as add_one = fn (n) -> n + 1 end | |
combine = &(&1 <> &2) # combines two string params | |
divrem = &{ div(&1, &2), rem(&1, &2)} # returns a tuple | |
# & helper for calling a function with given arrity | |
l = &length/1 | |
l.([1, 2, 3, 4]) # returns 4, same as calling length([1, 2, 3, 4]) | |
# gives us a nice way to pass functions to other functions | |
Enum.map [1, 2, 3], &(&1 + 1) # returns [2, 3, 4] | |
## Modules and Named Functions | |
# Defining a module | |
defmodule Times do | |
def double (n) do | |
n * 2 | |
end | |
end | |
# behind the scenes, this actually becomes | |
def double (n), do: n * 2 | |
# Compiling for iex | |
iex times.exs | |
# or | |
c "times.exs" # from within iex | |
# Using a module | |
Times.double 4 | |
# multiple arity for function | |
defmodule Factorial do | |
def of(0), do: 1 | |
def of(n), do: n * of(n-1) | |
end | |
# Guard Clauses | |
defmodule Guard do | |
def what_is(x) when is_number(x) do | |
IO.puts "#{x} is a number" | |
end | |
def what_is(x) when is_atom(x) do | |
IO.puts "#{x} is a atom" | |
end | |
end | |
# Default Parameters | |
defmodule Example do | |
def func(p1, p2 \\ 2, p3) do | |
IO.inspect [p1, p2, p3] | |
end | |
end | |
# Private Functions | |
defp foo | |
# Pipe Operator | |
defmodule Work do | |
def half(n), do: div(n, 2) | |
def plus_five(n), do: n + 5 | |
def squared(n), do: n * n | |
end | |
IO.puts (6) | |
|> Work.half | |
|> Work.plus_five | |
|> Work.squared | |
# More Modules | |
# Nested Modules | |
defmodule Outer do | |
defmodule Inner do | |
def inner_func do | |
end | |
end | |
def outer_func do | |
Inner.inner_func | |
end | |
end | |
# Module Directives | |
# import | |
# Brings modules functions or macros into current scope | |
import List, only: [ flatten: 1, duplicate: 2 ] | |
# will import List with only the flatten/1 and duplicate/2 functions | |
import List, except: [ flatten: 1 ] | |
# will import List with everything except the flatten/1 function | |
import List, only: [ :functions ] | |
# will import only functions for list | |
import List, only: [ :macros ] | |
# will import only macros for list | |
# alias | |
# Creates an alias for a module (helps cut down on typing) | |
alias My.Other.Module.Parser, as: Parser | |
Parser.parse(stuff) | |
# Above alias could have been abbreviated to: | |
alias My.Other.Module.Parser | |
# because as: parameter defaults to last part of module name | |
alias My.Other.Module.{Parser, Runnner} | |
# also works if you want to alias multiple items | |
# require | |
# If you want to use any macros a module defines | |
# more about this when we talk about Macros | |
# Module Attributes | |
# Modules attributes define meta data for a a module | |
@author "Chris Malven" | |
# Attributes can be referenced from within module | |
defmodule Example do | |
@author "Chris Malven" | |
def get_author do | |
@author | |
end | |
end | |
# Module Names (Elixir, Erlang, and Atoms) | |
# Modules in Elixir have names like String or PhotoAlbum. When you write a name starting with an uppercase letter (such as String or IO) Elixir converts it internally into an atom called Elixir.String or Elixir.IO | |
:"Elixir.IO" === IO # true | |
:"Elixir.IO".puts 123 | |
# List and Recursion | |
# the [ head | tail ] pattern makes it very easy to recursively modify lists: | |
defmodule MyList do | |
def add_1([]), do: [] | |
def add_1([ head | tail]), do: [ head+1 | add_1(tail) ] | |
end | |
# writing a map function using list recursion | |
defmodule MyList do | |
def map([], _func), do: [] | |
def map([ head | tail], func), do: [ func.(head) | map(tail, func) ] | |
end | |
MyList.map [1, 2, 3], &(&1 * &1) # [1, 4, 9] | |
# writing a sum function using list recursion | |
defmodule MyList do | |
def sum(list, do: _sum(list, 0)) | |
defp _sum([], total), do: total | |
defp _sum([ head | tail], total), do: _sum(tail, head + total) | |
end | |
# Maps, Keyword Lists, Sets, and Structs | |
# Keyword Lists | |
# Typically used in the context of options passed to functions | |
defmodule Canvas do | |
@defaults [ fg: "black", bg: "white" ] | |
def draw_text(text options \\ []) do | |
options = Keyword.merge(@defaults, options) | |
end | |
end | |
# Maps | |
# The go-to key/value structure, good performance at all sizes. | |
map = %{ name: "Dave", likes: "Programming", where: "Dallas" } | |
Map.keys map # [:likes, :name, :where] | |
Map.values map # ["Programming", "Dave", "Dallas"] | |
map[:name] # "Dave" | |
map1 = Map.drop map, [:where, :likes] # %{ name: "Dave" } | |
map2 = Map.put map1, :also_likes, "Ruby" # %{ name: "Dave", also_likes: "Ruby" } | |
Map.has_key? map1, :where # false | |
# Pattern Matching and Updating Maps | |
person = %{ name: "Dave", height: 1.2 } | |
# Is there an entry with the key :name? | |
%{ name: a_name } = person # %{ height: 1.2, name: "Dave" } | |
# Are there entries for the keys :name and :height? | |
%{ name: _, height: _ } = person # %{ height: 1.2, name: "Dave" } | |
# Does the entry with key :name have the value "Dave"? | |
%{ name: "Dave" } = person # %{ height: 1.2, name: "Dave" } | |
# Enumerating Maps | |
people = [ | |
%{ name: "Chris", role: "Designer" }, | |
%{ name: "Peter", role: "Engineer" }, | |
%{ name: "Andy", role: "Engineer" } | |
] | |
IO.inspect( | |
for person = %{ role: role } <- people, | |
role == "Engineer", # this is called a filter, and it is optional | |
do: person | |
) | |
# Map Pattern matching in function definitions | |
defmodule HotelRoom do | |
def book(%{ name: name, days: days }) | |
when days > 5 do | |
IO.puts "Apply weekly stay discount" | |
end | |
def book(%{ name: name, days: days }) | |
when days == 0 do | |
IO.puts "Your stay must be a day or longer" | |
end | |
def book(person) do | |
IO.puts "Your stay has been booked" | |
end | |
end | |
# Updating a map | |
# The simplest way to update a map is with this syntax | |
# (note that this can't add new keys to a map) | |
new_map = %{ old_map | key => value, … } | |
# Adding a new key to a map | |
Map.put_new(old_map, :foo, "bar") | |
# Structs | |
# A struct is just a module that wraps a limited form of map. | |
# The syntax for creating a struct is the same as for a map, | |
# but with a module name between the % and { | |
defmodule Subscriber do | |
defstruct name: "", paid: false, over_18: true | |
end | |
s1 = %Subscriber{} | |
s2 = %Subscriber{ name: "Dave" } | |
# Access the fields in a struct using dot notation or pattern matching | |
s2.name # "Dave" | |
%Subscriber{ name: a_name } = s2 | |
a_name # "Dave" | |
s4 = %Subscriber{ s2 | name: "Marie" } | |
# Structs are defined as modules because you can also add functions | |
defmodule Attendee do | |
defstruct name: "", paid: false, over_18: true | |
def may_attend_afterparty(attendee = %Attendee{}) do | |
attendee.paid && atendee.over_18 | |
end | |
end | |
a1 = %Attendee{ paid: true, over_18: false } | |
Attendee.may_attend_afterparty(a1) # false | |
# Processing Collections – Enum and Stream | |
# Enum | |
# Iterates (enumerates) over collections | |
# can convert any collection into a list… | |
list = Enum.to_list 1..5 # [1, 2, 3, 4, 5] | |
# concatenate collections… | |
Enum.concat([1, 2, 3], [4, 5, 6]) # [1, 2, 3, 4, 5, 6] | |
# create collections whose elements are a function of the original… | |
Enum.map(list, &(&1 * 10)) # [10, 20, 30, 40, 50] | |
# select elements by position or criteria… | |
Enum.at(10..20, 3) # 13 | |
Enum.filter(list, &(&1 > 2)) # [3, 4, 5] | |
Enum.filter(list, &Integer.is_even/1) # [2, 4] | |
# sort and compare elements… | |
Enum.sort | |
Enum.max | |
# split a collection | |
Enum.take | |
Enum.take_every | |
Enum.take_while | |
Enum.split | |
Enum.split_while | |
# Streams | |
# Composable enumerators, lazily evaluate | |
# to create a stream | |
s = Stream.map [1, 3, 5, 7], &(&1 + 1) # Stream<…> | |
# then to evaluate it, just treat it as a collection with Enum | |
Enum.to_list s | |
# you can compose multiple Streams together | |
s = Stream.map [1, 3, 5, 7], &(&1 + 1) | |
s2 = Stream.map s, &(&1 * 2) | |
s3 = Stream.map s2, &(&1 + &1) | |
Enum.to_list s3 # [8, 16, 24, 32] | |
# aka… | |
[1, 3, 5, 7] | |
|> Stream.map(&(&1 + 1)) | |
|> Stream.map(&(&1 * 2)) | |
|> Stream.map(&(&1 + &1)) | |
|> Enum.to_list | |
# and streams aren't just for lists | |
IO.puts File.open!("/usr/share/dict/words") | |
|> IO.stream(:line) | |
|> Enum.max_by(&String.length/1) | |
# Stream.cycle | |
# Cycles between list of values infinitely | |
Stream.cycle([1, 2, 3]) |> Enum.take(2) # [1, 2] | |
Stream.cycle([1, 2, 3]) |> Enum.take(5) # [1, 2, 3, 1, 2] | |
# Stream.repeatedly | |
# Takes a function and invokes it every time a new value is requested | |
Stream.repeatedly(&:random.uniform/0) |> Enum.take(3) | |
# [0.4435846174457203, 0.7230402056221108, 0.94581636451987] | |
# Comprehensions | |
# Maps and filters a collection into another collection | |
for x <- [1, 2, 3], do: x * x # [1, 4, 9] | |
for x <- [1, 2, 3], x < 3, do: x * x # [1, 4] | |
for x <- [1, 2], y <- [2, 1], do: {x, y} # [{2, 1}, {1, 1}, {2, 2}, {1, 2}] | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment