Comfortable learning curve for learning Elixir — part 2

🆒 Do not miss power tip in the end of the article!!! Improves development speed a lot 😎

Gaspar Chilingarov
Learn Elixir
4 min readJul 7, 2017

--

In first part I’ve covered work with basic data structures.

Now its time to learn power of pattern matching. This exercise should take 3–4 hours at most.

Write a simple stack machine (using finite state automaton) which reads commands from stdin and processes them. Stack machine starts with empty stack.

Commands are

  • push integer pushes integer on top of the stack
  • pop removes one element from top of the stack, should do nothing if called on empty stack
  • + — * / performs operation on two top elements in the stack and pushes result back to the stack
  • print top of the stack or EMPTY if stack is empty
  • (optional) swap — swaps two top elements of the stack.

Here are the snippets that you can use in this project:

Read input in the cycle

Save code in reader.exs

defmodule Reader do
def run() do
text = IO.gets "Enter text:"
IO.puts text
run()
end
end
Reader.run()

Run it with elixir reader.exs .

Converting input to commands

In order to have better performance and readability — first you need to convert commands to the internal presentation and then execute it.

Usually simplified language interpreter looks like:

tokenizer -> parser -> interpreter

In this exercise tokenizer is String.split

Parser may look like

def parser(["pop"]), do: :pop
def parser(["print"), do: :print

Command execution

And execution may look like very simple state machine.

@spec execute_command(command :: term, stack :: list) :: {return_code :: atom, stack :: list}
def execute_command(:pop, [_|t]) do
{:ok, t}
end

First line @spec describes arguments and return values of the function.

Function execute_command should take command and stack (old state) as input and return return_code and new stack as result tuple.

Control flow and decoupling functionality

The most obvious way to write code will be check what input arguments and state function got and based on that decide which step take next. While it is the easiest one, it adds cost.

Imagine that after some time you want to add logging to all error cases. You will need to find them all and add it in every place. That increases chances of failure and also adds redundant code.

Over the time it becomes spaghetti code which is very difficult to refactor.

There is beauty and also powerful decoupling logic in returning exit code and exit value/state instead of deciding which next step of recursion should be taken. In this way execute_command knows only about processing input data and generating output and does not change control flow by itself.

The function which calls execute_command and processes its return code and updates state can control how to react to each return code. So in case if you need to log all error return codes — you will add handler just in a single place in the controlling function.

This is powerful abstraction which allows you simplify code and make reasoning about it much easier. This lowers long-term costs of maintenance and support.

After you finish initial functionality — give extra consideration to edge cases:

  • what if divide by zero occurs?
  • what if user inputs unknown command?
  • what if number of arguments are wrong?
  • what if arguments are of wrong type (e.g. “push wrong_data”)

Do not forget to write tests for each function, because it will greatly help you with debugging and understanding code you write.

Simple test may look like

defmodule Test.StackMachine do
use ExUnit.Case
test "parser/1" do
assert(StackMachine.parser(["pop"]) == :pop
assert(StackMachine.parser(["print"]) == :print
end
end

Save it to test/stack_machine_test.exs and run mix test .

Power tip 😎😎😎

Having fast test and feedback cycle is essential for successful Test-Driven-Development. I usually aim to have feedback in less than 2 seconds from moment I save file. That allows me immediately understand if current change fixed problem or broke something.

For automatic test runs use amazing module mix test.watch . As author says

Because TDD is awesome

On Ubuntu you will need to sudo apt install inotify-tools to install necessary file system watching utilities.

This module will rerun specified tests when any file in the project is changed.

If you have many tests and they run slow — use mix test.watch test/file_name_test.exs to run test relevant to file you working right now on. And if even that is slow (you write really big program) — use mix test.watch test/file_name_test.exs:LINE_NUMBER to run one specific test.

Power combo with ExUnit Notifier

Combine mix test.watch with ExUnit Notifier and you will have nice popup messages every time when test run finished. This allows extremely quick feedback without even need of checking terminal where mix test runs, because you will immediately know if tests work or fail.

Follow instructions to install.

About The Author

I’m Gaspar Chilingarov . I facilitate DevOps transition, help moving legacy applications to cloud and write high-performance Elixir apps. You can connect with me on Twitter, Facebook, LinkedIn and GitHub.

Found this post useful? Kindly tap the ❤ button below! :) Let’s spread word about Elixir.

--

--

Gaspar Chilingarov
Learn Elixir

I facilitate DevOps transition, help moving legacy applications to the cloud and write high-performance Elixir apps.