Skip to content

Instantly share code, notes, and snippets.

@nathanjohnson320
Last active February 19, 2020 14:53
Show Gist options
  • Save nathanjohnson320/976c77ec409e120006f1ae430275052e to your computer and use it in GitHub Desktop.
Save nathanjohnson320/976c77ec409e120006f1ae430275052e to your computer and use it in GitHub Desktop.
# Elixir in 5 minutes
# To run this script, do elixir processes.exs
# Comments are made with the pound sign
# Strings are standard double quotes ""
# To write to the CLI you can use IO.puts or IO.write
IO.puts "Hello, world"
# Get input with IO.gets
input = IO.gets "IO.gets will take CLI input? (y/n)"
# Interpolate strings by using #{} inside double quotes. You can interpolate functions as well
IO.puts "I said: #{input}"
# Double quoted strings are also binaries. A binary is literally a sequence of bytes
IO.puts "Strings are binaries? #{is_binary(input)}"
# This will print hello with unicode characters since it translates codepoints to strings, elixir has excellent unicode support
IO.puts <<104, 101, 197, 130, 197, 130, 111, 0>>
# Pattern matching is the most productive part of the elixir language
# This is similar to destructuring in javascript but a lot more powerful
# time for the basic data types and pattern matching
# Elixir has numbers
IO.inspect 2
# And floats
IO.inspect 2.24123123123
# And booleans
IO.inspect true != false
IO.inspect not true == false # not == !
IO.inspect true and false != true # and == &&
IO.inspect true or false == true # or == ||
# And we already know it has strings
IO.inspect "This is a string"
# It also has character lists, this data type is primarily used for working with erlang modules
IO.inspect 'hełło' == [104, 101, 322, 322, 111]
# There are string constants called atoms, be careful when using them as they are not garbage collected
IO.inspect ":ok is an atom |> #{is_atom(:ok)}"
# Tuples are fixed data normally used for configurations
{ 1, 2 } = { 1, 2 }
my_tuple = { 1, 2 }
# Atoms in tuples are most commonly used to match the returned value of function
# Introducing the case statement which pattern matches multiple lines and returns on the first match
# and the |> (pipe) operator which sends the contents of the first variable
# as the first arg of the next function
case File.read("./swag") do
{:ok, contents} -> contents
{:error, code} -> "BAD #{code}"
end
|> IO.inspect
# You can assign variables to their corresponding match on the right hand side in tuples
{ 1, x } = my_tuple
IO.inspect x
# You can also get an element from an existing tuple, 2 should equal 2
IO.inspect elem(my_tuple, 1) == x
# This works for maps which are simple key value stores
# To access values in a map of this form use standard JS syntax my_map["my name"]
my_map = %{ "my name" => "is not rick", "swag" => "team", "not" => true}
IO.inspect my_map["my name"]
# You can match part of a map
%{ "my name" => name } = my_map
IO.puts "My name #{name}"
# Maps can also use atoms as keys but the whole map must use the same syntax, it can't have atom and
# string keys all at once. If you use an atom key then you can also access maps as my_map.swag
# It is also worth mentioning that since elixir is an immutable language this my_map
# and the one above are two different variables
my_map = %{ my_name: "is not rick", swag: "team", not: true}
IO.inspect my_map.swag
# Update maps has a special syntax
my_map = %{ my_map | my_name: "is Nathan", not: false }
IO.inspect my_map.not
# Elixir also has linked lists which we saw above with character lists
my_list = [100, 200, 300, 400]
# It also has the cons operator for destructuring lists as head and tail
[head | tail] = my_list
IO.puts "Head is 1, #{head}, Tail is [2,3,4]: #{tail}"
# you can match specific indices of a list as well
[ one, two, three | four ] = my_list
IO.inspect one
IO.inspect two
IO.inspect three
IO.inspect four # This will be a list
# Lists with atom indexes are called "keyword lists", underneath this is a list of two item tuples [{:beginning, true}, {:second, "second"}]
my_keywords = [ beginning: true, second: "second"]
IO.inspect my_keywords[:beginning]
# You can also leverage anonymous functions, note the "." before the arguments to easily identify that it is anonymous
my_func = fn(arg1) -> IO.puts arg1 end
my_func.("Hello, world!")
# Pattern matching works inside anonymous function args as well
my_func = fn({:ok, arg1}) -> IO.puts arg1 end
my_func.({:ok, "Hello, world of patterns!"})
# Now we know all the basics and can talk about processes which is the magic part of elixir/erlang
# Processes are lightweight threads that can be spawned, assigned to variables, monitored, and send messages to eachother
# which allows you to build gigantic fault tolerant applications
# start by spawning a process, it will be given a unique process identifier that is returned when the process terminates
my_process = spawn fn -> IO.puts "I'm a process" end
IO.inspect my_process
# Pause for one hundred milliseconds, note that you can directly work with erlang modules by accessing them as ":module"."function"
:timer.sleep 100
# After the process executes it dies and is garbage collected
IO.inspect Process.alive?(my_process)
# Messages can talk to each other with send and receive
process_1 = spawn fn ->
receive do
{:ok, msg} -> IO.inspect msg
{:error} -> IO.inspect "ERROR"
end
end
process_2 = spawn fn ->
send(process_1, {:ok, "HERES A MESSAGE FOR YA"})
end
# Wait a little bit for the messages to send
:timer.sleep 100
# Both processes are done
IO.inspect Process.alive?(process_1)
IO.inspect Process.alive?(process_2)
# You can get the current process' PID by calling self()
IO.inspect self()
# Now for modules, modules contain a set of functions as well as macros for abstracting/attaching functionality
# to other modules. You can have modules inside of modules too.
defmodule OuterModule do
defmodule MyMod do
# Structs are maps with default values and are declared under module namespaces
defstruct make: "Nissan", model: "Sentra", year: 2014
# the @doc macro allows you to place markdown in your code. Using mix docs (ex_doc module) allows
# you to generate html pages with these macros. These can only be put inside modules though
@doc ~S"""
# Here is a header
Here is a command
`cd home`
## Examples
You can even put iex commands in the docs as examples and these will be run as unit tests
iex> MyMod.print("CREATE shopping\r\n")
nil
"""
def print(args) do
IO.puts args
end
end
# Return a MyMod struct with the year overridden
def get_mod(year) do
%MyMod{year: year}
end
end
# You can invoke module functions as follows
OuterModule.MyMod.print("HELLO")
# Normally you can access structs as %<ModuleName>{} but since we're in a script
# we have to wrap the create of the struct in a function
IO.inspect OuterModule.get_mod(2016)
# Now that we understand the basics of modules I'll mention a few of the most
# useful in Elixir's stdlib
# Enum, (ee noom) you will use this module most frequently. It iterates over
# data types that have implemented the Enumerable protocol (a protocol is basically an interface)
# We'll also introduce range which generate every value from the first to the second
enumerable_list = 1..10
Enum.each(enumerable_list, fn(item) -> IO.puts "- #{item} - " end)
Enum.map(enumerable_list, fn(item) -> item * 2 end) |> IO.inspect
# Maps are enumerate in tuple format as {key, val}
enumerable_map = %{ a: 1, b: 2, c: 3, d: 4, e: 5 }
Enum.reduce(enumerable_map, 0, fn({_, val}, acc) -> acc + val end) |> IO.inspect
# Streams are similar to Enum but they are evaluated lazily, meaning only when called
# this way you can iterate over theoretically infinite datasets without running out
# of memory. To grab values from a stream you can use Enum.take, you can also leverage
# a good number of Enumerable functions (like reduce)
# Here's fibonacci as a stream, taking the first 50, and printing them out
Stream.unfold({1, 1}, fn {a, b} -> {a, {b, a + b}} end) |> Enum.take(50) |> IO.inspect
# Stream and Enum are both really useful for data iteration/aggregation.
# Elixir provides some other useful control flow/data aggregation constructs outside the stdlibs
# since these types of tasks are commonplace
# the first of these is "for" also called a "comprehension"
# for takes in a series of generator functions which are variables that you can use in reduction
# it also allows for "filter" statements to widdle down that list
for a <- 1..10, # Generate list of 10 items
b <- 1..10, # Generate list of 10 items
rem(a, 2) == 0, # Filter non-odd numbers from a
rem(b, 2) == 1, # Filter non-event numbers from b
do: {a, b} # Take a tuple of a, b elements
|> IO.inspect
# for is used for list generation and matching but there are a lot of times where
# we don't want to generate anything but rather handle a series of pattern matches for
# control flow. The mechanism for doing so is "for"s cousin "with". "with" helps you write
# code to handle the happy path
less_than_ten = fn(arg) ->
# Cond runs through a series of conditions and returns the first that evaulates to true
cond do
arg < 10 -> {:ok, arg}
arg >= 10 -> {:error, arg}
end
end
less_than_five = fn(arg) ->
cond do
arg < 5 -> {:ok, arg}
arg >= 5 -> {:error, arg}
end
end
is_four = fn(arg) ->
cond do
arg == 4 -> {:ok, arg}
arg != 4 -> {:error, arg}
end
end
# Instead of nesting a series of case statements we can match only
# the patterns that we want and if they don't match exactly then it'll
# fall into the else block.
four = with {:ok, arg} <- less_than_ten.(4),
{:ok, arg} <- less_than_five.(arg),
{:ok, arg} <- is_four.(arg) do arg
else
_ -> {:error, four}
end
IO.inspect four
not_four = with {:ok, arg} <- less_than_ten.(6),
{:ok, arg} <- less_than_five.(arg),
{:ok, arg} <- is_four.(arg) do arg
else
{:error, val} -> {:error, val}
_ -> {:error, nil}
end
IO.inspect not_four
# Here are a few honorable mention modules that you'll probably use
# Map
# Task
# OptionParser
# Regex
# Code <-- Be careful with this
# Macro <-- Be careful with this
# Mix
# Now that we know the language basics it's time to cover elixir's CLI tool Mix
# With any of these you can use the "help" command to print out useful info about
# whichever mix task you're trying. For example `mix help new`
# create a new app
# mix new <app>
# create new supervised app
# mix new <app> --sup
# run tests
# mix test
# Dependencies are accessible via git or in the standard repo hex.pm
# to add a dependency follow the instructions in the module's readme
# and place it under the dependencies function inside mix.exs
# Get dependencies
# mix deps.get
# Compile dependencies
# mix deps.compile
# Run a mix project with iex
# iex -S mix
# Run a mix app or script
# mix run
# Run a mix app or script without halting
# mix run --no-halt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment