Your first Elixir Project (Part 1)
Tags: elixir, functional, beginners, tutorial
Published: 2021-06-21 13:47:32

Let's write some Elixir!

## But before we start...

This tutorial series is designed to introduce developers to the Elixir programming language and get them up-and-running with a simple project.

If this is the first you've heard of Elixir, start with [my post that gives an overview of the language](https://www.brewinstallbuzzwords.com/posts/learn-elixir-with-me/).

This post assumes that you've already worked through [part 0 of this tutorial series](/posts/elixir-hands-on-tutorial-pt-00), which includes instructions for installing Elixir and creating the template project.

## Running the template project

Open a new terminal window and run the command `iex`. Now, you should be in the elixir REPL environment.

However, just because you're in the Elixir REPL doesn't automatically mean that you can run your code. `iex(1)> UnitConverter.hello()` gives an error.

Because Elixir is a compiled language, the function will be undefined until we compile it. For compiling, we have a couple options:

1. To compile a single file, run `c` followed by a string for the file path: `c "lib/unit_converter.ex"`. After making changes to the code, you would then re-compile the module by running `r` followed by the name of the module: `r UnitConverter`
2. Alternatively, if you are working within a project, you can open an iex shell with `iex -S mix` at the project's root directory. This will compile all modules in the project, allowing you to recompile all of them at once with the `recompile` command. 

While method 1 works just fine, I recommend using method 2 as I find it to be more convenient.

After compiling your code with either of the two methods, run `UnitConverter.hello()` again in iex. If all went well, you should see `:world` printed as the result.

*The value has a colon at the beginning because it uses the atom data type. We're not going to go over the details of elixir's data types in this post, but [here's the relevant documentation](https://elixir-lang.org/getting-started/basic-types.html#atoms) if you want to learn more.*

Here's a recap of what just happened:

- The file `lib/unit_converter.ex` has a module called `UnitConverter`
- You compiled that module and accessed it using `iex`, which is the REPL environment for elixir
- You ran the `hello` function in the `UnitConverter` module using dot notation
- That function returned the value `:world`, which caused that value to be printed to the terminal

*"But wait," you might be saying to yourself. "How did that value get returned? I don't see a `return` statement!"*

And you'd be right, there is no return statement. That's because, in elixir, functions automatically return the value of their last statement.

If you want to test that principle out for yourself, try adding another value before or after `:hello` and see what happens (don't forget to recompile before running the code again).

```elixir
def hello do
  :world
  :worlds
end
# returns :worlds

def hello do
  :world
  10
end
# returns 10

def hello do
  "there"
  :world
end
# returns :world
```

## Writing some code

Now that we're a bit more acquainted with how to compile and run elixir code, let's start writing some.

Throughout this tutorial, we're going to be writing functions that convert between units of measurement.

For our first function, we'll convert kilograms to grams:

```elixir
def kilograms_to_grams(x) do
  x * 1000
end
```

All this function does is multiply its input parameter `x` by 1000 and return that value.

Here's some things to note about this function:

1. `def` lets elixir know that you're writing a function
2. `do` signifies the end of the function header and the beginning of the function body
3. `end` tells elixir you've reached the end of the code block
4. The common convention is to use snake case for elixir function names, meaning everything is lowercase and there are underscores between each word

Now let's run this function.

You should still have `iex` open from earlier. If you don't, just run `iex -S mix`. Save your code and run `recompile` in `iex`. Then run `UnitConverter.kilograms_to_grams(10)`. If everything worked, you should see the output `10000`.

*🔥 hot tip 🔥*

*If you're tired of typing `UnitConverter` before every function call, you can run `import UnitConverter` in `iex` to call them without prefixing the module name. More information can be found [on this page of the documentation](https://elixir-lang.org/getting-started/alias-require-and-import.html). You only need to import a module once. If you run `recompile`, the updated code will be imported.*

## Time to test 🎊

Now that we've verified our function works for at least some values, it's time to write some unit tests so we can cover edge cases and ensure that the function continues to work after we make more changes.

In your text editor, open the file `test/unit_converter_tests.exs`. This is where test cases are written.

You should see an existing test for the `hello` function that was included in the file:

```elixir
test "greets the world" do
  assert UnitConverter.hello() == :world
end
```

Let's break this test down:

- Tests blocks start with the `test` keyword
- `"greets the world"` is the name of test, which is used to provide helpful logging about which tests passed
- `assert` is a function that causes the test to fail if its argument evaluates to `false`. Note that this function does not require parentheses
- All that's really going on here is that our test is verifying whether the value returned by `UnitConverter.hello()` is equal to `:world`

Now that we've gone over what this test does, let's actually run it!

Open a new terminal window and run the command `mix test`. If you haven't changed the `hello` function, then you should get a successful result that looks like this:

```elixir
..

Finished in 0.06 seconds
1 doctest, 1 test, 0 failures
```

Now let's try writing a test for the function we wrote above. From earlier, we know that we want  `UnitConverter.kilograms_to_gram(10)` to return `10000` so we'll start there.

```elixir
test "converts kilograms to grams" do
  assert UnitConverter.kilograms_to_grams(10) == 10000
end
```

Next, run the test using `mix test` and you should see a successful result that looks like this:

```elixir
...

Finished in 0.06 seconds
1 doctest, 2 tests, 0 failures
```

Before we add more functionality, let's write some more test cases to ensure our function works for other situations.

```elixir
test "converts kilograms to grams" do
  assert UnitConverter.kilograms_to_grams(10) == 10000
  assert UnitConverter.kilograms_to_grams(0) == 0
  assert UnitConverter.kilograms_to_grams(1.5) == 1500
end

test "kilograms to grams handles invalid input" do
  assert UnitConverter.kilograms_to_grams("hello there") == {:error, "invalid input"}
  assert UnitConverter.kilograms_to_grams(-1) == {:error, "invalid input"}
  assert UnitConverter.kilograms_to_grams([1, 2, 3]) == {:error, "invalid input"}
  assert UnitConverter.kilograms_to_grams(:invalid) == {:error, "invalid input"}
end
```

There's a few things going on in the updated test, so let's break it down:

- We added two new cases with the `"converts kilograms to grams"` test. These cases cover zero and decimal inputs.
- Next we created the test `"kilograms to grams handles invalid input"` which, as the name implies, has cases for different invalid inputs.
- The value `{:error, "invalid input"}` probably looks unfamiliar. In elixir, the common convention for errors is that a [tuple](https://hexdocs.pm/elixir/1.12/Tuple.html) is returned. The first value in the tuple is `:error`, and the second value is a message that explains the error.

If we run the tests again with `mix test`, we can see that we haven't handled everything we need to handle:

```elixir
1) test kilograms to grams handles invalid input (UnitConverterTest)
     test/unit_converter_test.exs:15
     ** (ArithmeticError) bad argument in arithmetic expression: "hello there" * 1000
     code: assert UnitConverter.kilograms_to_grams("hello there") == {:error, "invalid input"}
     stacktrace:
       :erlang.*("hello there", 1000)
       (unit_converter 0.1.0) lib/unit_converter.ex:20: UnitConverter.kilograms_to_grams/1
       test/unit_converter_test.exs:16: (test)

...

Finished in 0.1 seconds
1 doctest, 3 tests, 1 failure
```

From this output, we can see that we get an `ArithmeticError` when we try to call `kilograms_to_grams("hello there")`.

If you take a look back at that function in `lib/unit_converter.ex` you can see that we're using the `*` operator, which does not accept strings as an argument.

To ensure we only allow numbers to reach that point, we can check the type of the value in an if statement, using the `is_number` function. (For more information, see the docs [here](https://hexdocs.pm/elixir/Kernel.html#is_number/1))

```elixir
def kilograms_to_grams(x) do
  if is_number(x) do
    x * 1000
  else
    {:error, "invalid input"}
  end
end
```

Now let's run the tests again and see what we get:

```elixir
1) test kilograms to grams handles invalid input (UnitConverterTest)
     test/unit_converter_test.exs:15
     Assertion with == failed
     code:  assert UnitConverter.kilograms_to_grams(-1) == {:error, "invalid input"}
     left:  -1000
     right: {:error, "invalid input"}
     stacktrace:
       test/unit_converter_test.exs:17: (test)

...

Finished in 0.08 seconds
1 doctest, 3 tests, 1 failure
```

The test are still failing, but we get a different failure now. Progress!

The problem now is that we aren't checking to see if the input is non-negative. To fix this, we can add `and x >= 0` to our condition in the `if` statement.

```elixir
def kilograms_to_grams(x) do
  if is_number(x) and x >= 0 do
    x * 1000
  else
    {:error, "invalid input"}
  end
end
```

If you run the tests again, they should all pass!

```elixir
....

Finished in 0.09 seconds
1 doctest, 3 tests, 0 failures
```

## You did it!

You've now written your first elixir function and passed your unit tests! If you'd like to continue working on this project, I'll be releasing a part 2 where you will...

- Learn how to use the pipe operator!
- Use type guards to separate validation from functionality!
- Perform basic pattern matching!

To make sure you don't miss out, [follow me on DEV](https://dev.to/brewinstallbuzzwords) or [subscribe to my monthly newsletter](https://mailchi.mp/89dc077154c7/brewinstallbuzzwords).

## More content

If you liked this, you might also like some of my other posts:

* [Changing Emoji Skin Tones Programmatically](https://dev.to/brewinstallbuzzwords/changing-emoji-skin-tones-programmatically-4667)
* [I made my first svg animation!](https://dev.to/brewinstallbuzzwords/i-made-my-first-svg-animation-29j3)
* [5 tips for publishing your first npm package](https://dev.to/brewinstallbuzzwords/5-tips-for-publishing-your-first-npm-package-3e4a)
* [4 Hugo Beginner Mistakes](https://dev.to/brewinstallbuzzwords/4-hugo-beginner-mistakes-90h)
* [Introducing meme-type: A translator for internet speak](https://dev.to/brewinstallbuzzwords/introducing-meme-type-a-translator-for-internet-speak-i6a)