Elixir for Python programmers - UNFINISHED DRAFT

Please send corrections to ivan@ludios.org.

Python Elixir Notes
Overview
Websites
First appeared 20 February 1991 May 2012, running on a platform that appeared in 1986.
VM Typically runs on its VM, whether CPython or PyPy. Runs on the Erlang virtual machine (BEAM). IronPython and Jython run in foreign VMs.
Multi-core concurrency Multi-core execution in the same Python process is possible with threads, but because of the global interpreter lock, only one thread can be executing Python bytecode. Other threads can wait on IO or call into C code, but they cannot run more Python code (exception: pypy-stm). Multi-core execution is possible using Erlang processes, which are preempted by the Erlang VM. Erlang processes receive and send messsages without sharing data structures. They can be linked into a hierarchy, monitored with tools like etop, and support per-process memory limits.
Garbage collection The Erlang VM gives each Erlang process its own heap. For garbage collection, the Erlang VM uses a "a per-process generational semi-space copying collector using Cheney's copy collection algorithm together with a global large object space".
Pattern matching No general pattern matching except through third-party libraries. Pattern matching is built into the VM and can be used with `=`, `case`, function arguments, lists, binaries, and more. A pattern matching tutorial in Erlang.
File extensions
  • `.py` for source files
  • `.pyc`, `.pyo` for compiled bytecode
  • `.ex` for source files that are to be compiled
  • `.exs` for source files that are to be interpreted
  • `.beam` for compiled BEAM bytecode
When to use .ex and .exs files
REPL
Start the REPL
  • `python`
  • `python3`
  • ipython for some use cases
  • `iex`
  • `iex -S mix` to start with modules loaded from your mix project
Exit the REPL
  • Ctrl-d on Linux
  • Ctrl-z, Return on Windows
  • Ctrl-c, Ctrl-c
  • Ctrl-c, a, Enter
  • Ctrl-g, q, Enter
Get last evaluation in REPL
			_
			

(underscore)

			v()
			
Get help for a function
			help(func)
			
			h func
			
Package and project managers
pip and virtualenv mix, which puts dependencies in your project's `deps/` folder
Auditing deps `pip install` executes code in downloaded dependencies without giving you a chance to audit them. `mix deps.get` downloads but doesn't execute code in packages from Hex, giving you a chance to audit them before compiling (and thus executing) them.
Types and data structures
Atoms No direct equivalent `:some_name` is an atom, a constant symbol that does not need pre-declaration. Atoms are not garbage-collected and there is a default maximum of 1,048,576 atoms, so you must be careful not to create a large quantity of atoms dynamically.
Booleans and null
				True
				False
				None
			
				true
				false
				nil
			
Elixir `true`, `false`, and `nil` are equivalent to the atoms `:true`, `:false`, and `:nil`.
Lists `[1, 2, 3]` is a mutable dynamic array `[1, 2, 3]` is a linked list
Tuples `(1, "2", 3)` `{1, "2", 3}`
Maps `{"x": 3, "y": 3.5}` is a mutable hash table `%{"x" => 3, "y" => 3.5}` is an immutable map Large Erlang/Elixir maps use a HAMT (hash-array mapped trie)
Strings
  • `"string"` is a bytestring in Python 2.x, and unicode in Python 3.x
  • `u"string"` is unicode
  • `b"string"` is a bytestring
  • `"string"` is an Erlang binary with UTF-8 encoding
  • `'string'` is an Erlang list with 6 codepoints,
    `[115, 116, 114, 105, 110, 103]`
Syntax and common operations
Calling Function calls require `()` Function calls usually don't require `()`, so `IO.puts "hi"` and `IO.puts("hi")` are equivalent.
Implicit return Functions return `None` if there is no `return` statement. Functions return the last expression. Elixir has no `return` statement; instead, use `if`, `case`, or `cond`.
Named functions Named functions can be defined at the top-level module scope, in a class, or in another function.

Named functions can not be defined at the top-level scope (including the REPL) or in another function; they can only be in a `defmodule` declaration:

			defmodule MyModule do
			  def add(x, y) do
			    x + y
			  end
			end

			MyModule.add(2, 3)
			# => 5
			
"It's because there is a bug in Erlang."
Multiple declarations Defining another function with the same name will just overwrite the previously defined function.

Elixir supports defining multiple functions of the same name with different patterns or arities:

			defmodule MyModule do
			  def op("double", n) do
			    n + n
			  end

			  def op("square", n) do
			    n * n
			  end

			  def op(n) do
			    n * n * n
			  end
			end

			MyModule.op("double", 10)
			# => 20
			MyModule.op("square", 10)
			# => 100
			MyModule.op(10)
			# => 1000
			
Optional arguments

Optional arguments must be listed last:

			def add(x, y=3):
			    return x + y

			add(2)
			# => 5
			

Even though Python's collections and objects are generally mutable, default parameters e.g. `y=dict()` are evaluated only when the function is defined, not on every function call.

Optional arguments can be listed at any position, as long as they do not conflict with an existing declaration:

			defmodule MyModule do
			  def add(x, y \\ 3) do
			    x + y
			  end
			end

			MyModule.add(2)
			# => 5
			

or

			defmodule MyModule do
			  def add(x \\ 10, y) do
			    x + y
			  end
			end

			MyModule.add(2)
			# => 12
			

With both examples above, Elixir defines two separate functions, `MyModule.add/1` and `MyModule.add/2`.

Anonymous functions
			square = lambda n: n ** 2
			square(10)
			# => 100
			
			square = fn n -> n * n end
			square.(10)
			# => 100
			
Why are there two kinds of functions in Elixir?
print with newline
			print(obj)
			
			IO.puts(obj)
			
print w/o newline
			print(obj, end='')
			
			IO.write(obj)
			
repr `repr(obj)` `inspect(obj)`
`print(repr(obj))` `IO.inspect(obj)`, which also returns `obj` after printing.
Pipe operator

No pipe operator, unless you want to use macropy. In many cases, of course, the returned object has a relevant method for further processing:

			" hello world ".strip().upper()
			# => 'HELLO WORLD'
			

The `|>` pipe operator works like Clojure's `->` thread-first macro:

			" hello world " |> String.trim |> String.upcase
			# => "HELLO WORLD"
			

which is equivalent to:

			String.upcase(String.trim(" hello world "))    
			# => "HELLO WORLD"
			
Exponent `**` exponentiation operator works on both `int`s/`long`s and `float`s No exponentiation operator; `:math.pow` exists but always returns a float. An integer exponentiation function for Elixir.
Mapping
				list(n * n for n in [1, 2, 3, 4])
				# => [1, 4, 9, 16]

				list(map(lambda n: n * n, [1, 2, 3, 4]))
				# => [1, 4, 9, 16]
			
				for n <- [1, 2, 3, 4], do: n * n
				# => [1, 4, 9, 16]

				Enum.map([1, 2, 3, 4], fn n -> n * n end)
				# => [1, 4, 9, 16]
			
Elixir comprehensions.
Mapping with a named function
				list(map(str.upper, ["hello", "world"]))
				# => ['HELLO', 'WORLD']
			

The function must be captured with the & operator:

				["hello", "world"] |> Enum.map(&String.upcase/1)
				# => ["HELLO", "WORLD"]
			
assert
			assert x == 3, "not 3"
			

Elixir doesn't have an `assert` in Kernel, but you can use the one in ExUnit.Assertions:

			import ExUnit.Assertions, only: [assert: 1, assert: 2]

			assert x == 3, "not 3"
			

You can also "assert" with pattern matching or by pinning a variable with `^`. The downside is that may get a less-informative error on failures:

			x = 2

			^x = 3
			# => ** (MatchError) no match of
			# =>    right hand side value: 3

			{:ok, val} = {:fail, nil}
			# => ** (MatchError) no match of
			# =>    right hand side value: {:fail, nil}
			
String operations
strip
			' text \n'.strip()
			# => 'text'
			
			" text \n" |> String.trim
			# => "text"
			
Elixir `trim`* functions operate on graphemes and strip all Unicode whitespace.
rstrip
			' text \n'.rstrip()
			# => ' text'
			
			" text \n" |> String.trim_trailing
			# => " text"
			
lstrip
			' text \n'.lstrip()
			# => 'text \n'
			
			" text \n" |> String.trim_leading
			# => "text \n"
			
split
			' hello world '.split(' ')
			# => ['', 'hello', 'world', '']
			
			" hello world " |> String.split(" ")
			# => ["", "hello", "world", ""]
			
split max=N
			'hello world again'.split(' ', 1) # max splits
			# => ['hello', 'world again']
			
			"hello world there" |> String.split(" ", parts: 2)
			# => ["hello", "world there"]
			
upper
			'hello world'.upper()
			# => 'HELLO WORLD'
			
			"hello world" |> String.upcase
			# => "HELLO WORLD"
			
lower
			'HELLO WORLD'.lower()
			# => 'hello world'
			
			"HELLO WORLD" |> String.downcase
			# => "hello world"
			
Replace all occurences
			'hello world'.replace("l", "x")
			# => 'hexxo worxd'
			
			"hello world" |> String.replace("l", "x")
			# => "hexxo worxd"
			
Replace first match
			'hello world'.replace("l", "x", 1)
			# => 'hexlo world'
			
			"hello world" |> String.replace("l", "x", global: false)
			# => "hexlo world"
			
Repeat / duplicate
			'*' * 20
			# => '********************'
			
			"*" |> String.duplicate(20)
			# => "********************"
			
Count graphemes Unsupported
			"\u0067\u0308" |> String.length
			# => 1
			# Note: g̈
			
O(N) in Elixir
Count codepoints
			len(u"\u0067\u0308")
			# => 2
			
			"\u0067\u0308" |> String.to_charlist |> length
			# => 2
			
O(1) in Python,
O(N) in Elixir
Count bytes
			len(u"\u0067\u0308".encode('utf-8'))
			# => 3
			
			"\u0067\u0308" |> byte_size
			# => 3
			
O(N) in Python with encoding step,
O(1) in Elixir
needle `in` haystack
			"ello" in "hello world"
			# => True
			
			"hello world" |> String.contains?("ello")
			# => true
			
startswith
			'hello world'.startswith('hello')
			# => True
			
			"hello world" |> String.starts_with?("hello")
			# => true
			
endswith
			'hello world'.endswith('hello')
			# => False
			
			"hello world" |> String.ends_with?("hello")
			# => false