Your First Elixir Project (Part 2)
Tags: elixir, functional, beginners, tutorial
Published: 2022-01-05 12:00:49

## Before we get started...

This is part 2 of a series for building your first project in Elixir. If you’re brand new to Elixir, [start with my overview of the language](https://www.brewinstallbuzzwords.com/posts/learn-elixir-with-me/).

This post assumes you’ve already completed parts 0 and 1 of this series already.

- [Language overview](https://www.brewinstallbuzzwords.com/posts/learn-elixir-with-me/)
- [Part 0](https://www.brewinstallbuzzwords.com/posts/elixir-hands-on-tutorial-pt-00/)
- [Part 1](https://www.brewinstallbuzzwords.com/posts/elixir-hands-on-tutorial-pt-01/)

## Now, where were we?

At the end of [Part 1](https://www.brewinstallbuzzwords.com/posts/elixir-hands-on-tutorial-pt-01/), we had written and tested a few functions for converting between units of measure.

We had also added some error handling for invalid input and wrote some tests for that too.

In this part, we’ll be re-writing some of our existing code to utilize type guards. We’ll also be writing some new functions and using pipes.

## Our existing code

If you’ve been following along from the beginning, you should have a `kilograms_to_grams/1` function that looks like this:

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

There’s nothing wrong with this as-is, but Elixir has some tools to help us eliminate some of this nesting.

Using [guards](https://hexdocs.pm/elixir/main/patterns-and-guards.html#guards), we can limit our function to only run when the input is valid. Here’s what that would look like:

```elixir
def kilograms_to_grams(x) when is_number(x) and x >= 0 do
  x * 1000
end
```

Guards start with `when` and are followed by a boolean expression. Only when the expression is `true` will the function execute.

To test this, let’s see what happens when we run our tests with `mix test` and the invalid input tests are reached:

```elixir
1) test kilograms to grams handles invalid input (UnitConverterTest)
     test/unit_converter_test.exs:15
     ** (FunctionClauseError) no function clause matching in UnitConverter.kilograms_to_grams/1
```

From this message, we can see that it never reached the inside of the function. Instead, we get an error that there was no matching function clause.

## Handling invalid input

To make sure we still handle invalid input, we need to define another function with the same name and arity, but without the guards:

```elixir
def kilograms_to_grams(x) when is_number(x) and x >= 0 do
  x * 1000
end

def kilograms_to_grams(x) do
  {:error, "invalid input"}
end
```

When a call to `kilograms_to_grams/1` is made, it will call our first function if the input is a number and greater than or equal to zero. Otherwise, it will default to our error function.

Let’s make sure we did this right and run our tests again:

```elixir
....

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

Great, we still pass our tests!

But a closer look will reveal that we also get a syntax warning:

```elixir
warning: variable "x" is unused (if the variable is not meant to be used, prefix it with an underscore)
```

## Ignoring function parameters

We need to accept a parameter in order for it to be a fallback for our guarded version of `kilograms_to_grams/1`. Removing the parameter would cause us to instead have `kilograms_to_grams/0`, which would break our intended control flow.

But we also don’t need to do anything with this parameter when it doesn’t meet the criteria of our guard.

Luckily, the warning tells us how to fix the issue. Prefixing a parameter name with an underscore will tell the compiler that we need to accept the parameter, but that it won’t be used within the function.

```elixir
def kilograms_to_grams(x) when is_number(x) and x >= 0 do
  x * 1000
end

def kilograms_to_grams(_x) do
  {:error, "invalid input"}
end
```

If we run the tests again now, the warning should be resolved.

## Adding more conversions

Now that we have this conversion working, let’s follow the same pattern for converting ounces to grams and pounds to ounces. Because this code is essentially the same as what we’re using in `kilograms_to_grams/1` but with different conversion factors, feel free to just copy and paste these tests and functions.

Add these tests to `unit_converter_test.exs`:

```elixir
test "converts ounces to grams" do
  assert Float.floor(UnitConverter.ounces_to_grams(10), 4) == 283.4951
  assert UnitConverter.ounces_to_grams(0) == 0
  assert Float.floor(UnitConverter.ounces_to_grams(1.5), 4) == 42.5242
end

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

test "converts pounds to ounces" do
  assert UnitConverter.pounds_to_ounces(10) == 160
  assert UnitConverter.pounds_to_ounces(0) == 0
  assert UnitConverter.pounds_to_ounces(1.5) == 24
end

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

Note that these tests make use of `[Float.floor/2](https://hexdocs.pm/elixir/1.13/Float.html#floor/2)` to round the results of `ounces_to_grams/1` for testing.

Add these functions to `unit_converter.ex`:

```elixir
def ounces_to_grams(x) when is_number(x) and x >= 0 do
  x * 28.34952
end

def ounces_to_grams(_x) do
  {:error, "invalid input"}
end

def pounds_to_ounces(x) when is_number(x) and x>= 0 do
  x * 16
end

def pounds_to_ounces(_x) do
  {:error, "invalid input"}
end
```

## Changing things up a bit

Next we’re going to write a function to convert pounds to grams. But instead of looking up the conversion factor and plugging it into a function, we’re going to leverage our existing functions `pounds_to_ounces/1` and `ounces_to_grams/1`.

We’ll start the same way by writing some simple tests:

```elixir
test "converts pounds to grams" do
  assert Float.floor(UnitConverter.pounds_to_grams(10), 2) == 4535.92
  assert UnitConverter.pounds_to_grams(0) == 0
  assert Float.floor(UnitConverter.pounds_to_grams(1.5), 2) == 680.38
end

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

Then we’ll write our function utilizing the same techniques we’ve used above:

```elixir
def pounds_to_grams(x) when is_number(x) and x >= 0 do
  ounces = pounds_to_ounces(x)
  ounces_to_grams(ounces)
end

def pounds_to_grams(_x) do
  {:error, "invalid input"}
end
```

The only difference here is that instead of multiplying or dividing our input value, we:

- Pass the input into `pounds_to_ounces/1`
- Store the result in a variable named `ounces`
- Pass `ounces` into `ounces_to_grams/1`

Alternatively, we could nest the function calls:

```elixir
def pounds_to_grams(x) when is_number(x) and x >= 0 do
  ounces_to_grams(pounds_to_ounces(x))
end
```

## But there’s a better way

The above code is fine, but Elixir has an operator that lets you avoid nested function calls or creating variables that are only used once.

## Pipes

The [pipe operator](https://elixirschool.com/en/lessons/basics/pipe_operator), `|>`, works by passing the return value of one call as the first parameter in the next call.

Here’s the above function rewritten using pipes:

```elixir
def pounds_to_grams(x) when is_number(x) and x >= 0 do
  pounds_to_ounces(x)
  |> ounces_to_grams()
end
```

First, `pounds_to_ounces(x)` is evaluated. Then, the `|>` operator passes the return value into the first (and only) parameter of `ounces_to_grams/1`.

While this concept may feel foreign at first, it’s really helpful for when you need to make sequential calls to modify some data.

## Adding pipes to our tests

We can also eliminate some function call nesting in our tests for `ounces_to_grams/1` and `pounds_to_grams/1`. 

```elixir
test "converts ounces to grams" do
  assert UnitConverter.ounces_to_grams(10) |> Float.floor(4) == 283.4951
  assert UnitConverter.ounces_to_grams(0) == 0
  assert UnitConverter.ounces_to_grams(1.5) |> Float.floor(4) == 42.5242
end
```

```elixir
test "converts pounds to grams" do
  assert UnitConverter.pounds_to_grams(10) |> Float.floor(2) == 4535.92
  assert UnitConverter.pounds_to_grams(0) == 0
  assert UnitConverter.pounds_to_grams(1.5) |> Float.floor(2) == 680.38
end
```

## You did it!

Now you know how to use type guards and pipes in Elixir. You’ve also written a small module that you can add more of your own functions to.

To make sure you don’t miss the next part of this series or any of my other Elixir content, [subscribe to my monthly newsletter](https://mailchi.mp/89dc077154c7/brewinstallbuzzwords), [follow me on Dev](https://dev.to/brewinstallbuzzwords), or [follow me on Twitter](https://twitter.com/brewbuzzwords).

## More Content

If you liked this, you might also like some of my other posts. If you want to be notified of my new posts, [follow me on Dev](https://dev.to/brewinstallbuzzwords) or subscribe to my [brief monthly newsletter](https://mailchi.mp/89dc077154c7/brewinstallbuzzwords).

* [What's the best questions you've been asked in a job interview?](https://dev.to/brewinstallbuzzwords/whats-the-best-question-youve-been-asked-in-a-job-interview-4a1j)
* [What was the first program you wrote?](https://dev.to/brewinstallbuzzwords/what-was-the-first-program-you-wrote-1gjp)
* [The weird quirk of JavaScript arrays (that you should never use)](https://dev.to/brewinstallbuzzwords/the-weird-quirk-of-javascript-arrays-that-you-should-never-use-4l3b)
* [Does Elixir have for loops?](https://www.brewinstallbuzzwords.com/posts/does-elixir-have-for-loops/)
* [Learn Elixir with me!](https://www.brewinstallbuzzwords.com/posts/learn-elixir-with-me/)
* [Project Tours: Bread Ratio Calculator](https://www.brewinstallbuzzwords.com/posts/bread-ratio-calculator/)
* [Changing Emoji Skin Tones Programmatically](https://www.brewinstallbuzzwords.com/posts/emoji-skin-tone/)
* [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://www.brewinstallbuzzwords.com/posts/tips-for-first-npm-package/)
* [4 Hugo Beginner Mistakes](https://www.brewinstallbuzzwords.com/posts/hugo-beginner-mistakes/)