Day Four & Five - Feeling It

Picture shows Jason A. Martin - Software Engineer, Indie Game Developer, Tech Evangelist, Entrepreneur.

Awesomesauce

I'm nearing a full week into the Elixir world and I'm starting to feel it. There's still a lot that's hazy and tons to learn, but I'm starting to feel those basic concepts sink in. It's most likely because I'm reviewing several training services, messing around in IEX and watching some conference videos.

The weekend is approaching and I'm planning on jumping back into Programming in Elixir 1.3. But we shall see. I'm also itching to create a command-line project so I can start playing with more pieces (verses one-off code checks and such).

Here's some of the stuff I've been learning and messing with lately:

Documentation

So you're getting going with your Elixir modules & functions and are wondering how to document stuff. Fear not! It's super simple.

@moduledoc and @doc are your friends. Once you use them to document your code, you can do fun things, such as use help on the command line for your modules & functions to see the documentation. Here's a quick example:

def module SuperHero do
  @moduledoc """
    This is my cool hero module that does stuff (not really).
  """

  @doc """
    My super-technical description for this function here.
  """"
  def favorite_hero(name) do
    "I like ${name}"
  end
end

Tip: Elixir uses Markdown for docs. If you're not up-to-speed on Markdown Basics, check that link.

Tip 2: Check out the official Writing Documentation page from Elixir-Lang and check out the "Recommendations" section for some good tips.

When you're documenting your functions, make sure they are public. Elixir doesn't like you documenting private functions. The language wants you to document private functions with comments rather than @doc.

You can also use ExDoc, which takes all your @doc and @moduledoc information and turns them into HTML docs!

Heads or Tails?

Spent some time with Lists and learning how Elixir processes them. So if you have a List [1, 2, 3, 4, 5, 6], you can work on that list from different angles (front/back). And how you work with the List is important.

Internally, Elixir is storing each item in the List with a reference to the next item in the list, so you end up with a head/tail pattern.

For this reason, prepending a List is much cheaper than appending the list.

Example 1:

# Cheap prepend to head
list = [1, 2, 3]
[0 | list] # => [0, 1, 2, 3]

In this case, Elixir went to the head of the list and put [0] before it. Nice n cheap.

Example 2:

# expensive append to tail
# note: ++ is operator for concatenating two lists (-- is other way ex: list -- [2] = [1, 3])
list = [1, 2, 3]
list ++ [4] # => [1, 2, 3, 4]

This is more expensive. Why? As I understand it, Elixir is going to the head item (1 in this case) and checking for the next reference (2 in this case). It then goes to that reference and checks for the reference to the next item and so on.

Now, in this case and being called once, it's not a big deal. But, it is something to know for when things get bigger.

As a little side note, you can use head/tail splitting in a couple ways:

list = [1, 2, 3, 4, 5]

[head | tail] = list # => head is 1 and tail is [2, 3, 4, 5]
[first, second | tail] = list # => first = 1, second = 2, tail = [3, 4, 5]

Note that when you're pulling out first, second, head in the above example, you're getting the element itself and not a list (tail is the remaining list).

Pattern Matching Again & Some Further Code Enhancements

I continue to enjoy learning about all the ways pattern matching is great. Over the past couple of days I've been looking into how to code in Elixir better and here are a few tips and thoughts.

#####If you find yourself using many ifs and if/else and if/ else if/ else, you're doing it wrong (most likely). Between pattern matching, guards, etc, the use of "if" is going to be much less than in other languages.

Instead of peppering your code with "if" this then that, look into cond.

Here's an example of using cond instead of "if-else if-else":

  def best_super_hero(name) do
    cond do
      name == "Batman" -> IO.puts("Bats!")
      name == "Superman" -> IO.puts("How many people did you kill Superman? How many!")
      name == "Wonder Woman" -> IO.puts("Tell the truth!")
      true -> IO.puts("Awesome!")
    end
  end

  best_super_hero("Batman") # => "Bats!"

In the above example, the function takes a name and outputs some text based on the name. If there's no match, it outputs "Awesome!" instead.

Also we can clean it up by making functions for the things. One refactor example:

  name == "Batman" -> batman

When the condition is met, the function named batman is called.

It's important to note that there isn't any passdown happening. Once a condition is met, the cond is terminated.

The above code could be refactored even further to be more readable! Let's do that next.

#####Embrace Pattern Matching

In our last example, we dumped IF in favor of COND. This was a nice improvement for readability, but it could still be much better. The best_super_hero function is a little messy and too big. We glady repeat ourselves slightly if readability and debugging get a boost. Let's make that happen.

We're going to use Elixir's pattern matching on functions (overriding). Instead of having one function that does logic inside of it to figure things out, we will simple make a few functions and let Elixir do the logic.

Refactor 1:

  def best_super_hero("Batman"), do: IO.puts("Go bats go!")
  def best_super_hero("Wonder Woman"), do: O.puts("Tell the truth!")
  def best_super_hero("Superman"), do: IO.puts("How many people did you kill Superman? How many!")
  def best_super_hero(_anything_else), do: IO.puts("Others are great too!")

This is so much cleaner and easier to read.

When we call best_super_hero and pass it an argument, we have 4 possible outcomes:

  1. best_super_hero("Batman") => "Go bats go!"
  2. best_super_hero("Wonder Woman") => "Tell the truth!"
  3. best_super_hero("Superman") => "How many people did you kill Superman? How many!"
  4. best_super_hero("sdfjksdfkjhsahfjkshfkdhk") => "Others are great too!"

That 4th option is our "catch all". If pattern matching fails, this will be the version of the function used.

Let's clean this up a little more.

Refactor 2:

  def best_super_hero("Batman"), do: batman
  def best_super_hero("Wonder Woman"), do: wonder_woman
  def best_super_hero("Superman"), do: superman
  def best_super_hero(_anything_else), do: all_other_heroes

This is very readable and does what we want (after making the batman/etc functions of course).

Guards

It gets even better. You can use "guards" for your functions to given them even more power/flexibility.

Say for example you are calling a function where if the argument passed in is going to be one thing if that argument is A, B or C and another thing for all others. How would you do that? Here's one way:

  def dc_heroes(name) when name in ["Batman", "Wonder Woman", "Superman"], do: proper_hero
  def dc_heroes(_all_others), do: other_characters

In this example, when you call dc_heroes(name) you get the proper_hero function if the name is in that List. And if not, you get the other_characters function.

Pretty sweet!

NOTE: I recommend reading up on Elixir Guards to see what you can and can't do with them.

Random Notes

Here are some random notes from things I've learned or have been messing with over the past couple of days.

#####Turn Off Variable Re-Binding

One thing Elixir does that Erlang doesn't is re-binding.

Re-bind in Elixir:

x = 3 # => x will be 3
x = 4 # => x will now be 4

Under the hood, Elixir isn't changing 3 to 4. Instead, it's making a new memory location for 4 and then changing the memory reference of x to point to it instead of the previous one (3 in this case).

If you want to disallow this, use the caret ^.

No Re-Bind in Elixir:

x = 3 # => x is now 3.

^x = 4 # FAILS because pattern matching can't make this true since we locked x down (3)

#####Underscore Don't Care

In case you missed it, I used a leading underscore (_) in the argument examples above. Why?

The underscore character itself tells the compiler that the input can be anything. You just don't care. Here's a quick example:

[_, x] = [1, 2] # => x is 2

You can add characters to the underscore for readability as I did above as long as the underscore is the first character. Here are some examples:

_
_hey
_dc_rocks
_no_cares_to_give

And this is why the underscore is used in my above functions that were there to catch pattern matching fails.

Here's a quick example of using underscores in a case matching situation:

case {1, 2, 3} do
  {66, 33, 1} -> fun_one
  {_, 2, 3} -> nice # => will be true when any element is with 2,3
  {_, _, _} -> oh_my

In the above example, the 3 underscore match will only be used if the first two match attempts failed. Additionally, instead of {_, _, _}, just use true (true -> oh_my).

#####Read a File

You can read a file with File.read(filename). The return is a tuple with either an :ok Atom and the content or an :error Atom.

{status, content} = File.read("some_file.txt")

# status => :ok
# content => content of the file.

And if the file wasn't there, status would be :error and content would be :enoent

#####Splitting Strings

To split a string, you can use String.split. Here's a quick demo:

def first_name(name) do
  name
    |> String.split
    |> List.first
end

first_name("Jim Smith") # => "Jim"

As you may have guest, List.first returns the first item in the list. You can also use List.last to get the last item.