Elixir 1.3

Chapter 6 

Modules and
Named Functions

Module and Named Function

defmodule​ Times ​do​
​  ​def​ double(n) ​do​
​    n * 2
​  ​end​
​​end​

We have a module named Times and it contains a single function double. Since this function has a
single param it's name is identified as double/1.

Compiling a module

$ iex times.exs
​​iex>​ Times.double 4
​8

If you’re already in iex, you can use the c helper to compile your file without returning to the command line.

​​iex>​ c ​"​​times.exs"​
​[Times]
​​iex>​ Times.double(4)
​8
​​iex>​ Times.double(123)
​246

Function Failure

​iex>​ Times.double(​"​​cat"​)
​​**​ (ArithmeticError) bad argument in arithmetic expression
​    times.exs:3: Times.double/1

An exception (ArithmeticError) gets raised,
and we see a stack backtrace. 

The first line tells us what went wrong,
and the second line tells us where. 
Notice, the name of our function: Times.double/1.

Named Functions

In Elixir a named function is identified by both its name and its number of parameters (its arity). 
 

Our double function takes one parameter, so Elixir knows it as double/1
 

If we had another version of double that took three parameters, it would be known as double/3
 

These two functions are totally separate as far as
Elixir is concerned. 

The Function’s Body Is a Block

The do…end form is just a lump of syntactic sugar—during compilation it is turned into the do: form.

Typically people use the do: syntax for single-line blocks, and do…end for multiline ones.

​defmodule​ Times ​do​
​  ​def​ double(n) ​do​
​    n * 2
​  ​end​
​​end​

defmodule​ Times ​do​
​   ​def​ double(n), ​do​: n * 2
​​end​

​defmodule​ Times, ​do​: (​def​ double(n), ​do​: n*2)

Function Calls and Pattern Matching

Named functions use pattern matching to bind their parameter list to the passed arguments just like anonymous functions but with named functions we can write the function multiple times, each with it's own parameter list and body.

defmodule​ Factorial ​do​
​  ​def​ of(0), ​do​: 1
​  ​def​ of(n), ​do​: n * of(n-1)
​end​

What happens when we call Factorial.of(2)?

Function Calls and Pattern Matching

defmodule​ Factorial ​do​
​  ​def​ of(0), ​do​: 1
​  ​def​ of(n), ​do​: n * of(n-1)
​end​

This pattern of design and coding is very common in Elixir (and almost all functional languages).

First look for the simplest possible case, one that has a definite answer. This will be the anchor.

Then look for a recursive solution that will end up calling the anchor case.

Function Calls and Pattern Matching

​defmodule​ BadFactorial ​do​
  ​def​ of(n), ​do​: n * of(n-1)
  ​def​ of(0), ​do​: 1
​end​

The order of these clauses can make a difference when you translate them into code. Elixir tries functions from the top down, executing the first match.

​iex>​ c ​"​​factorial1-bad.exs"​
.../factorial1-bad.ex:3: this clause cannot match because a previous clause at
                         line 2 always matches

Elixir helps out:

Guard Clauses using when

​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_list(x) ​do​
​    IO.puts ​"​​#{​inspect(x)​}​​ is a list"​
​  ​end​
​  ​def​ what_is(x) ​when​ is_atom(x) ​do​
​    IO.puts ​"​​#{​x​}​​ is an atom"​
​  ​end​
​end​
​   
​Guard.what_is(99)        ​# => 99 is a number​
​Guard.what_is(​:cat​)      ​# => cat is an atom​
​Guard.what_is([1,2,3])   ​# => [1,2,3] is a list​

In pattern matching a function,
what if we need to distinguish based on their
types or on some test involving their values?

Guard Clauses

# old example
​​defmodule​ Factorial ​do​
​  ​def​ of(0), ​do​: 1
​  ​def​ of(n), ​do​: n * of(n-1)
​​end

# checking for positive numbers
​​defmodule​ Factorial ​do​
​  ​def​ of(0), ​do​: 1
​  ​def​ of(n) ​when​ n > 0 ​do​
​    n * of(n-1)
​  ​end​
​​end​
iex>​ c ​"​​factorial2.exs"​
​[Factorial]
​​iex>​ Factorial.of -100
​​**​ (FunctionClauseError) no function clause matching in Factorial.of/1...

Guard-Clause Limitations

You can write only a subset of Elixir expressions in guard clauses. There are many operators and the following:

Type-check functions, return true/false
is_atom is_binary is_bitstring is_boolean is_exception is_float is_function is_integer is_list is_map is_number is_pid is_port is_record is_reference is_tuple

Return values
abs(number) bit_size(bitstring) byte_size(bitstring) div(number,number) elem(tuple, n) float(term) hd(list) length(list) node() node(pid|ref|port) rem(number,number) round(number) self() tl(list) trunc(number) tuple_size(tuple)

Default Parameters

  1. When calling a function with default parameters, Elixir compares the number of arguments you are passing with the number of required parameters for the function. 
  2. If you’re passing fewer arguments than the number of required parameters, then there’s no match. 
  3. If the two numbers are equal, then the required parameters take the values of the passed arguments, and the other parameters take their default values. 
  4. If the count of passed arguments is greater than the number of required parameters, Elixir uses the excess to override the default values of some or all parameters. 
  5. Parameters are matched left to right.

Default value looks like param \\ value

Default Parameters

The first function definition (with the default parameters) matches any call with two, three, or four arguments.

defmodule​ Example ​do​
​  ​def​ func(p1, p2 \\ 2, p3 \\ 3, p4) ​do​
​    IO.inspect [p1, p2, p3, p4]
​  ​end​
​​end​
​
​Example.func(​"​​a"​, ​"​​b"​)             ​# => ["a",2,3,"b"]​
​Example.func(​"​​a"​, ​"​​b"​, ​"​​c"​)        ​# => ["a","b",3,"c"]​
​Example.func(​"​​a"​, ​"​​b"​, ​"​​c"​, ​"​​d"​)   ​# => ["a","b","c","d"]​
def​ func(p1, p2 \\ 2, p3 \\ 3, p4) ​do​
​  IO.inspect [p1, p2, p3, p4]
​​end​
​
​​def​ func(p1, p2) ​do​
​  IO.inspect [p1, p2]
​​end​

​** (CompileError) default_params.exs:7: def func/2 conflicts with
​                  defaults from def func/4”

Private Functions

The defp macro defines a private function—one that can be called only within the module that declares it.

defmodule Module do
  ​def​ fun(a) ​when​ is_list(a), ​do​: true
  defp​ fun(a), ​do​: false
end

The Amazing Pipe Operator: |>

people = DB.find_customers
​orders = Orders.for_customers(people)
​tax    = sales_tax(orders, 2016)
​filing = prepare_filing(tax)
# or
filing = prepare_filing(sales_tax(Orders.for_customers(DB.find_customers), 2013))

Elixir has a better way of writing it.

filing = DB.find_customers
​           |> Orders.for_customers
​           |> sales_tax(2016)
​           |> prepare_filing

The |> operator takes the result of the expression to its left and inserts it as the first parameter of the function invocation to its right

So the list of customers the first call returns becomes the argument passed to the for_customers function. 

The Amazing Pipe Operator: |>

​iex>​ (1..10) |> Enum.map(&(&1*&1)) |> Enum.filter(&(&1 < 40))
​[1, 4, 9, 16, 25, 36]

val |> f(a,b) is basically the same as calling f(val,a,b). You can also use chain the calls on the same line.

Note that use of parentheses in that code—the & shortcut and the pipe operator fight otherwise.

You should always use parentheses around function parameters in pipelines.

 

Programming is transforming data, and the |> operator makes that transformation explicit.

Modules

defmodule​ Mod ​do​
​  ​def​ func1 ​do​
​    IO.puts ​"​​in func1"​
​  ​end​
​  ​def​ func2 ​do​
​    func1
​    IO.puts ​"​​in func2"​
​  ​end​
​end​
​   
​Mod.func1
​Mod.func2

If we want to reference a function defined in a module from outside that module, we need to prefix the reference with the module’s name

Nested Modules

defmodule​ Outer ​do​
​  ​defmodule​ Inner ​do​
​    ​def​ inner_func ​do​
​    ​end​
​  ​end​
​
​  ​def​ outer_func ​do​
​    Inner.inner_func
​  ​end​
​end​
​   
​Outer.outer_func
​Outer.Inner.inner_func

Module nesting in Elixir is an illusion—all modules are defined at the top level. Elixir simply prepends the outer module name to the inner module name by putting a dot between the two.

Directives for Modules

Elixir has three directives that simplify
working with modules.

All three are executed as your program runs, and the effect of all three is lexically scoped
it starts at the point the directive is encountered, and
stops at the end of the enclosing scope.

 

  1. import
  2. alias
  3. require

import Directive

The import directive brings a module’s functions and/or macros into the current scope.

The full syntax of import is
import Module [, only:|except: ]

defmodule​ Example ​do​
​  ​def​ func1 ​do​
​     List.flatten [1,[2,3],4]
​  ​end​
​  ​def​ func2 ​do​
​    ​import​ List, ​only:​ [​flatten:​ 1]
​    flatten [5,[6,7],8]
​  ​end​
​end

You write only: or except:,
followed by a list of name: arity pairs.

alias Directive

The alias directive creates an alias for a module.

defmodule​ Example ​do​
​  ​def​ compile_and_go(source) ​do​
​    alias My.Other.Module.Parser, ​as:​ Parser
​    alias My.Other.Module.Runner, ​as:​ Runner
​    source
​    |> Parser.parse()
​    |> Runner.execute()
​  ​end​
​​end​

We could have abbreviated these alias directives to
  alias My.Other.Module.Parser
​  alias My.Other.Module.Runner

because the as: parameters default to the last part of the module name. We could even take this further, and do:
​  alias My.Other.Module.{Parser, Runner}

require Directive

You require a module if you want to use any macros it defines.

This ensures that the macro definitions are available when your code is compiled.

We’ll talk about require when we talk about macros.

Module Attributes

Elixir modules each have associated metadata. Each item of metadata is called an attribute of the module and is identified by a name.

You can access these attributes by prefixing the name with an at sign (@).

This works only at the top level of a module—you can’t set an attribute inside a function definition.

You can, however, access attributes inside functions.

 

The format is:

@name  value

Module Attributes

This works only at the top level of a module—you can’t set an attribute inside a function definition.
You can access attributes inside functions.

​​defmodule​ Example ​do​
​  @author  ​"​​Dave Thomas"​
​  ​def​ get_author ​do​
​    @author
​  ​end​
​​end​
​IO.puts ​"​​Example was written by ​​#{​Example.get_author​}​​"​

Module Attributes

You can set the same attribute multiple times in a module. If you access that attribute in a named function in that module, the value you see will be the value in effect when the function is defined.

​defmodule​ Example ​do​
​  @attr ​"​​one"​
​  ​def​ first, ​do​: @attr
​  @attr ​"​​two"​
​  ​def​ second, ​do​: @attr
​end​
​IO.puts ​"​​#{​Example.first​}​​  ​​#{​Example.second​}​​"​ # => one  two​”

Use them for configuration and metadata only. They are like a constant in other languages.

Module Names:
Elixir, Erlang, and Atoms

Internally, module names are just atoms. When you write a name starting with an uppercase letter, such as IO, Elixir converts it internally into an atom called Elixir.IO.

iex>​ is_atom IO
​true
​iex>​ to_string IO
​"Elixir.IO"
​iex>​ ​:"Elixir.IO"​ === IO
​true

So a call to a function in a module is really an atom followed by a dot followed by the function name

iex>​ IO.puts 123
​123
​iex>​ ​:"Elixir.IO"​.puts 123
​123”

Calling a Function in an Erlang Library

The Erlang conventions for names are different—variables start with an uppercase letter and atoms are simple lowercase names.

 

The Erlang module timer is called just that, the atom timer

In Elixir we write that as :timer
If you want to refer to the tc function in timer, you’d write :timer.tc. (Note the colon at the start.)

iex>​ ​:io​.format(​"​​The number is ~3.1f~n"​, [5.678])
​The number is 5.7
​:ok

Thank you!

Programming Elixir 1.3 Chapter 06

By Dustin McCraw

Programming Elixir 1.3 Chapter 06

  • 1,177