Skip to content

Instantly share code, notes, and snippets.

@mcmire
Last active March 17, 2017 11:10
Show Gist options
  • Save mcmire/f816119c0166e18c95d8 to your computer and use it in GitHub Desktop.
Save mcmire/f816119c0166e18c95d8 to your computer and use it in GitHub Desktop.
Elixir notes

Elixir notes

Things I picked up while reading the getting started guide:

  • You can sort two arbitrary data types: 1 < atom evaluates to true.
  • + isn't overloaded like it is in Ruby -- it's only used for arithmetic. If you want to add two arrays together, use ++; two strings, use <>.
  • Single-quoted strings are character lists, and lists that are composed of ASCII character codes will be represented as single-quoted strings
  • Double-quotes strings are actually a different data type than single-quoted strings so 'foo' != "foo"
  • Lists are linked lists, tuples are stored contiguously in memory.
  • If you see "size" it's a performant operation, if you see "length" then you know it will scale as the data structure grows.
  • Use and / or for comparing boolean values, use && / || for comparing non-booleans.
  • | is the "head/tail" operator -- it builds a list using the first element as the head and the second list as the tail. However, it can also be used on the left hand side to split a list into head and tail variables.
  • when is powerful -- it lets you place constraints on arguments
  • Pattern matching can be used inside functions to provide multiple entrypoints into that function
  • Control flow is completely different, and use pattern matching heavily; if / unless are macros
  • do/end blocks are always bound to the outermost function call (actually similar to Ruby)
  • The string concatenation operator (<>) actually concatenates two "binaries" (a "binary" is a datatype in Elixir, and a double-quoted string is a binary).
  • You can make a binary with << ... >>
  • You can also have what's called "bitstrings" -- these are just a series of bits. You can make one using <<1 :: size(1)>>
  • You can also use <> on the LHS of a match -- "he" <> rest = "hello" will result in rest == "llo"
  • A "keyword list" is a list of 2-element tuples where each tuple starts with an atom. [a: 1, b: 2] is shorthand for [{:a, 1}, {:b, 2}]. Since they are lists, you can do list-y things with them such as concatenation and |.
  • Keyword lists are NOT maps: they are ordered, and keys may be specified more than once.
  • When you are passing keyword arguments to a function, you're using a keyword list
  • For maps that have atoms as keys, you can use dot notation to access values (map.a is the same as map[:a]). The difference is that if the key doesn't exist, the dot notation will raise an error, whereas when using brackets, it will return nil.
  • You can also use | to concatenate maps (%{existing_map | :a => 2})
  • Both keyword lists and maps implement the Dict behavior, and therefore this module can be used as a more abstract data type.
  • defp denotes private functions
  • & can be used to grab a reference to a function (whether in the global namespace or under a module), so you can call it directly.
  • & can also be used to quickly create an anonymous function -- &(&1 + 1) is the same as fn(x) -> x + 1 end
  • You can declare argument defaults with \\ e.g. def join(a, b \\ nil)
  • *.ex files are designed to be compiled into bytecode and then used as a library; *.exs files are meant to be either executed using the elixir executable or accessed using exs.
  • All data structures in Elixir are immutable <3
  • Functions may have multiple "clauses", and those clauses may have guards. This is similar to Haskell or other ML-based functional languages.
  • Similar to Ruby or Clojure, the Enum module is an abstraction around working with "enumerables". Enumerables are: lists, maps, ranges, etc. So the functions that Enum provide are generic ways to work with these data structures, although data structures have their own methods, too.
  • All functions in Enum are eager. If you don't want that, you can use the Stream module which provides equivalent methods that are lazy instead, except they return Stream objects which can be accessed.
  • A stream is also an enumerable too, so you can use the Enum module to take an item from a Stream.
  • send is used to send a message to an arbitrary process. A message can be a tuple, a string, or whatever. receive looks for a message that matches a pattern; if it can't find one, it blocks and waits until it gets one.
  • An error raised in a process will not bring down the parent process unless you use spawn_link
  • It's better to use the Task module rather than straight spawn or spawn_link because it provides slightly better error messages and gives you back a tuple of {status, pid} that you can pattern-match against.
  • You can give a name to a process using Process.register. This allows you to pass an atom to send instead of a pid
  • The Agent module is an abstraction around using a process to hold state, all immutably.
  • Similar to Ruby, functions in the standard library may end with a ?, which indicates a boolean, or !, which indicates that an error might occur when using the function
  • In the case of File.read/2 vs File.read!/2, the first version returns a tuple of {status, contents} whereas the second version just returns the contents (or raises an error)
  • Processes are used to open files. This means that if two processes need to read/write to the same file then all they need access to is the process id.
  • Similar to Ruby, the StringIO implements the IO interface where the source is a String
  • alias, require, and import are lexically scoped
  • require takes a module name, not a file path
  • import lets you refer to functions without using their fully qualified name

to be continued...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment