Functions in Elixir

Sunday, 8th November, 2015

** defined functions

Functions are defined within a module.

Note that definition syntax and usage behaviour is slightly different depending on whether the function has a single argument or several arguments. For example, a space between function name and arguments is acceptable for a single-argument function, but is a syntax error with a multi-argument function:

def clown2a(x) do 5 * x end  # ok

def clown2b (x) do 5 * x end  # ok

def clown2b x do 5 * x end  # ok

def clown3a(x,y) do x + y end  # ok

def clown3b (x,y) do x + y end  # syntax error

def clown3c x,y   do x + y end  # ok

Similarly when using functions:

iex(1)> Drop.clown2a(5)
5

iex(2)> Drop.clown2a (5)
5

iex(3)> Drop.clown3a(5,6)
11

iex(4)> Drop.clown3a (5,6)
** (SyntaxError) iex:81: unexpected parentheses. If you are making
a function call, do not insert spaces between the function name
and the opening parentheses. Syntax error before: '('

There is also an optional short form of function definition which uses the construction “, do:” instead of “do” and omits “end”. This form can be used if the function body fits on one line, e.g.:

[7]  def clown4b(x,y), do: x + y 

[8]  def clown4b(x,y), do:
         x + y

Note that this short form is much stricter about spaces and brackets, but does still allow some creativity and individualism for the programmer:

def clown4d (x,y), do:  x + y  
# syntax error - space after function name

def clown4e x,y,   do:  x + y  
# syntax error - no brackets round arguments

def clown4c(x,y),  do:x + y    
# syntax error - no space after "do:"

def clown4h(x,y) , do : x + y  
# syntax error - space between "do" and ":"

def clown4f(x,y) , do:  x + y  # ok - space around ","

def clown4g(x,y)  ,do:  x + y  # ok - no space after ","

Functions defined above are available in this gist. They are based on code from the O’Reilly book “Introducing Elixir”.

** anonymous functions

In the shell only anonmyous functions can be used, e.g. bound to a variable.

Anonymous functions seem to behave similarly in the shell and in modules.

Anonymous functions do not behave quite the same as “defined” functions.

*** remember the extra dot

When using a bound anonymous function you must add an extra dot between the function name and its arguments:

 iex(1)> ad5 = fn(x) -> 5+x end
 #Function
 iex(2)> ad5(9)
 ** (CompileError) iex:94: undefined function ad5/1 

 iex(3)> ad5.(9)
 14

*** optional spaces almost everywhere, almost like defined functions

As with defined functions, there are lots of options for adding whitespace — but not quite the same options:

**** one parameter

 iex(4)> a5b = fn (x) -> 5+x end
 #Function
 iex(5)> a5b.(9)
 14
 iex(6)> a5b. (9)
 14
 iex(7)> a5b. 9
 ** (SyntaxError) iex:7: syntax error before: 9

 # cf:
 iex(8)> :math.sqrt(9)
 3.0
 iex(9)> :math.sqrt (9)
 3.0
 iex(10)> :math.sqrt 9
 3.0

*** two parameters

 iex(11)> sma = fn(x,y) -> x+y end
 #Function
 iex(12)> smb = fn (x,y) -> x+y end
 #Function
 iex(13)> smc = fn x,y -> x+y end
 #Function
 iex(14)> smc.(4,5)
 9
 iex(15)> smc. (4,5)
 9
 iex(16)> smc. 4,5
 ** (SyntaxError) iex:14: syntax error before: 4

cf similar defined functions in a module:

defmodule Clown do

  def clown3a(x,y)  do IO.puts(x+y) end
# def clown3b (x,y) do IO.puts(x+y) end  # syntax error
  def clown3c x,y   do IO.puts(x+y) end

end

Running this module:

 iex(17)> Drop.clown3a(5,6)
 11
 :ok
 iex(18)> Drop.clown3a (5,6)
 ** (SyntaxError) iex:81: unexpected parentheses. If you are making a
 function call, do not insert spaces between the function name and
 the opening parentheses. Syntax error before: '('
 
 iex(19)> Drop.clown3a 5,6
 11
 :ok

Command #15 is ok but command #18 is a syntax error;
Command #16 is a syntax error but command #19 is ok.

*** short version

There is also a short version of the anonymous function, in which the arity is implicit:

 iex(20)> clownx = &(&1 + &2)
 &:erlang.+/2
 iex(21)> clownx. (4,5)
 9
 iex(22)> clownx. (4,5,6)
 ** (BadArityError) &:erlang.+/2 with arity 2 called with 3 arguments (4, 5, 6)
 
 iex(23)> clowny = &(&1 + &3)
 ** (CompileError) iex:3: capture &3 cannot be defined without &2
     (elixir) src/elixir_fn.erl:123: :elixir_fn.validate/4
     (elixir) src/elixir_fn.erl:112: :elixir_fn.do_capture/4

The module compiler does do some of this checking. In the module below, the commented-out function c1a would be blocked by the compiler, but function c1b will not be. At runtime, the running process will crash when it reaches line 10.

 defmodule Anon do
 
 #  def c1a(x,y,z) do
 #   f = &(&1 + &3)  # syntax error blocked by compiler
 #   f. (x,y,z)
 #  end
 
   def c1b(x,y,z) do
    f = &(&1 + &2)
    f. (x,y,z)
   end
 
 end

For example:

 iex(24)> c("anon.ex")
 [Anon]
 iex(25)> Anon.c1b(2,3,4)
 ** (BadArityError) &:erlang.+/2 with arity 2 called with 3 arguments (2, 3, 4)
     anon.ex:10: Anon.c1b/3

A morning with elixir

Saturday, 1st June, 2013

After reading Joe Armstrong’s recent blog post on elixir, and the ensuing discussion there and on twitter, I’ve thought a bit about why I don’t like the language. I can’t spend a week with elixir, so the three observations below are after a morning’s reading and watching.

My overall impression is that elixir is a language that makes it easy for people with a ruby background to dabble with erlang. However, after a morning I’ve found at least three things about elixir that would make my own programming life less pleasant (see below). Elixir contains apparently powerful new features (e.g., macros and protocols), but the examples I’ve seen (e.g. in José Valim’s Erlang Factory talk) are not very exciting.

word-like forms as delimiters


  # elixir
  [1]  def name ( arglist ) do code end

  % erlang
  [2]  name ( arglist ) -> code .

I fail to see how [1] is an improvement on [2]. [2] is shorter by a handful of characters but, mainly, [1] uses word-like forms as delimiters, which I think is a very bad idea.

A human reader will read those word-like forms as words (e.g., the English words “do” and “end”), which they’re not. The delimiters “def”, “do”, and “end” delimit the function definition in the same way that “(” and “)” delimit the argument list.

Using word-like forms as delimiters will either/both (a) slow down the human reader as they read these forms as words, or/and (b) make the code harder to read, as the human reader must explicitly remember not to interpret certain word-like forms as words.

cf also lc and inlist or inbits used to delimit comprehensions.

n.b.: the same arguments apply to erlang’s use of “end” as a delimiter for fun and case blocks. Presumably erlang’s excuse was that they ran out of delimiters borrowed from English punctuation, and they didn’t want to overload “.”. I wonder whether some kind of list delimiter might be appropriate (for case at least), e.g.:


  R = case f() of
        [this -> 1;
         that -> 2],
  g(R).

The pipeline operator |>

The pipeline operator strikes me as being halfway between useful and troublesome. It would probably be very useful in cases similar to the example Joe gives (repeated below for convenience) — where a series of functions each take and produce a single item of data.


  capitalize_atom(X) ->
    V1 = atom_to_list(X),
    V2 = list_to_binary(V1),
    V3 = capitalize_binary(V2),
    V4 = binary_to_list(V3),
    binary_to_atom(V4).

  def capitalize_atom(X) do
    X |> atom_to_list
      |> list_to_binary
      |> capitalize_binary 
      |> binary_to_list
      |> binary_to_atom
  end

However, I think if any of the functions in the series take more than one argument, things could quickly get cumbersome. Witness the discussion on Joe’s blog, Joe’s recent tweets suggesting improvements, and the caveat in the elixir documentation.

The simple case above could be done in erlang with a fold:


  capitalize_atom(X) ->
    lists:foldl(fun(F, Acc) -> F(Acc) end, X, 
                [fun atom_to_list/1, 
                 fun list_to_binary/1,
                 fun capitalize_binary/1,
                 fun binary_to_list/1,
                 fun binary_to_atom/1]
               ).

Granted, this is possibly even yuckier than Joe’s version, but using a fold or, more generally, writing a function that walks through a list of funs, gives the possibility of handling errors, using some kind of context dictionary as the passed argument, etc.

I think elixir’s “|>” looks more general than it usefully is.

atoms

In erlang, variables must begin with a capital letter; atoms need special marking only if they start with a capital letter, or contain certain characters, in which case the atom is enclosed in single quotes. In erlang, module names and function names are atoms.

In elixir, variables don’t seem to need any special marking. Atoms /sometimes/ need to be marked with an initial colon. Unless the atom is a function or module name. Unless the module/function is from the erlang standard library. I might have that wrong.