Skip to content

Instantly share code, notes, and snippets.

@cmalven
Created June 25, 2016 13:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cmalven/0446ce01a6cb27658d5e940f0a8b5be0 to your computer and use it in GitHub Desktop.
Save cmalven/0446ce01a6cb27658d5e940f0a8b5be0 to your computer and use it in GitHub Desktop.
Programming Elixir Notes
# 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