# My Elixir journey

My journey to learn Elixir

Heya!! This is me learning Elixir and documenting what I find interesting along the way. Before digging you may want to learn more about who I am:

<table data-view="cards"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><a href="https://petros.blog/about
">https://petros.blog/about<br></a></td><td></td><td></td><td><a href="https://petros.blog/about">https://petros.blog/about</a></td><td><a href="/files/I98Wrb1oGRNlgmSszPNF">/files/I98Wrb1oGRNlgmSszPNF</a></td></tr><tr><td><a href="https://petros.blog/now/">https://petros.blog/now/</a></td><td></td><td></td><td><a href="https://petros.blog/now/">https://petros.blog/now/</a></td><td><a href="/files/W7TwBpyqMbuF97I8wpcT">/files/W7TwBpyqMbuF97I8wpcT</a></td></tr><tr><td><a href="https://world.hey.com/petros/">world.hey/petros</a></td><td></td><td></td><td><a href="https://world.hey.com/petros/">https://world.hey.com/petros/</a></td><td><a href="/files/Xh7g93yA6Q0JAkQgdmqv">/files/Xh7g93yA6Q0JAkQgdmqv</a></td></tr><tr><td></td><td><a href="https://dev.to/petros">https://dev.to/petros</a></td><td></td><td><a href="https://dev.to/petros">https://dev.to/petros</a></td><td><a href="/files/PxHhvi3Q5qe3TiFvSBRa">/files/PxHhvi3Q5qe3TiFvSBRa</a></td></tr></tbody></table>

### My Elixir journey

* [My public profile on Exercism](https://exercism.org/profiles/petros/solutions)
* [Exhort - Exercism - August 2022](/exhort)

### Bits and pieces

* [Clean mix dependencies](/bits-and-pieces/clean-mix-dependencies)
* [Run tests automatically on save](/bits-and-pieces/run-tests-automatically-on-save)
* [Run tests and stop on first failure](/bits-and-pieces/run-tests-and-stop-on-first-failure)
* [How to remove Tailwind from a Phoenix project](/bits-and-pieces/how-to-remove-tailwind-from-a-phoenix-project)


# Why functional programming?

Why do I like functional programming?

For some reason, I really love functional programming. Although my whole career has been involving object oriented programming and the imperative paradigm: Delphi, C#, Java, Ruby etc.

Ever since I met Elixir, something is attracting me to it. The idea of immutability with functions that always return the same output given the same input seems so good.

But overall, I wasn't sure why I was drawn to this.

### My 1998 thesis

Then I remembered my 1998 thesis:

<figure><img src="/files/zeH6fN3RqSLdqfT9VhuX" alt=""><figcaption><p>ZTrans: A Semi-Automatic Translator from Z to SML</p></figcaption></figure>


# Exhort

Exhort on Exercism

In August 2022, Exercism held a 30 day Elixir Exhort. Basically, a cohort of people that have a common goal of learning Elixir.

Read more about it:

{% embed url="<https://exercism.org/cohorts/exhort-aug-22>" %}
Exhort August 2022 edition
{% endembed %}

You can find all my notes and the solutions for each day in the subpages that follow.

{% hint style="warning" %}
**Possible spoiler alert:** When you visit the subpages, you may see spoilers in my notes. The main solution code is always hidden though.
{% endhint %}


# Day 22

Monday, 29 August 2022 - Day 22 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [ ] [Stack Underflow](#stack-underflow)

### Stack Underflow

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/stack-underflow) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/stack-underflow/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule RPNCalculator.Exception do
  defmodule DivisionByZeroError do
    defexception message: "division by zero occurred"
  end

  defmodule StackUnderflowError do
    defexception message: "stack underflow occurred"

    @impl true
    def exception(value) do
      case value do
        [] ->
          %StackUnderflowError{}

        _ ->
          %StackUnderflowError{message: "stack underflow occurred, context: " <> value}
      end
    end
  end

  def divide(stack) when length(stack) < 2, do: raise(StackUnderflowError, "when dividing")
  def divide([0, _nominator] = _stack), do: raise(DivisionByZeroError)
  def divide([divisor, nominator]) when divisor != 0, do: nominator / divisor
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/0154390a5bd57a27c963371a36cbc02b)

</details>

#### Notes

## Overall progress

<figure><img src="/files/QElOV9qYBLPREk4Uq88m" alt="An image showing my progress on Exercism. It&#x27;s 23.1% as of Monday, 29 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 21

Sunday, 28 August 2022 - Day 21 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [Top Secret](#top-secret)

### Top Secret

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/top-secret) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/top-secret/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule TopSecret do
  @spec to_ast(String.t()) :: tuple()
  def to_ast(string), do: Code.string_to_quoted!(string)

  @spec decode_secret_message_part(tuple(), list()) :: tuple()
  def decode_secret_message_part(ast, acc) when elem(ast, 0) in [:def, :defp] do
    ast
    |> elem(2)
    |> Enum.at(0)
    |> get_function_ast()
    |> get_function_name_and_arity()
    |> get_sliced_name()
    |> get_secret_message_part(ast, acc)
  end

  def decode_secret_message_part(ast, acc), do: {ast, acc}

  defp get_secret_message_part(name, ast, acc), do: {ast, [name | acc]}

  defp get_function_ast(ast) when elem(ast, 0) == :when do
    ast
    |> elem(2)
    |> Enum.at(0)
  end

  defp get_function_ast(ast), do: ast

  defp get_function_name_and_arity(function_ast) do
    fname =
      function_ast
      |> elem(0)
      |> Atom.to_string()

    {fname, get_arity(function_ast)}
  end

  defp get_arity(function_ast) when elem(function_ast, 2) == nil, do: -1

  defp get_arity(function_ast) do
    function_ast
    |> elem(2)
    |> Enum.count()
    |> Kernel.-(1)
  end

  defp get_sliced_name({_fname, arity}) when arity == -1, do: ""
  defp get_sliced_name({fname, arity}) when arity > -1, do: fname |> String.slice(0..arity)

  @spec decode_secret_message(String.t()) :: String.t()
  def decode_secret_message(string) do
    string
    |> to_ast()
    |> Macro.postwalk([], &decode_secret_message_part/2)
    |> Tuple.to_list()
    |> Enum.at(1)
    |> Enum.reverse()
    |> Enum.join()
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/8c6b0effcd78d9e1b3f1c9f4da7299b9)

</details>

#### Notes

I struggled with this one a lot. My first version of the code was not idiomatic at all. It is too verbose and relies a lot on `Enum.at` and `elem`. That's in comparison to using pattern matching.

In my second iteration I've borrowed some improvements from others. Namely, checking if the expression is a function with `a in [:def, :defp]` which I was checking using a conditional statement before.

But I still decided to keep my code instead of using pattern matching. Because of the complexity of the AST data structure, the pattern matching is rather complicated and I feel it's not as readable. Despite it being shorter. But this is really just a preference. Most consider the solutions that rely on pattern matching heavily more elegant.

I have also learned about [mix\_test\_watch | Hex](https://hex.pm/packages/mix_test_watch) and I have started using it. It helps me run all tests on every save. I install the dependency:

```elixir
# mix.exs (v1.13)
def deps do
  [
    {:mix_test_watch, "~> 1.0", only: :dev}
  ]
end
```

I configure it in my project:

```elixir
# config/config.exs
import Config

if config_env() == :dev do
  config :mix_test_watch,
    clear: true
end
```

Then I start the watcher in iTerm:

```shell
mix test.watch --seed 0 --max-failures 1 --include pending
```

I have VSCode and iTerm side by side and I work on my code. On every save, the tests run in iTerm. I have reduced using `iex` dramatically as I am adding `IO.inspect` statements that immediatelly show up in iTerm after each save.

## Overall progress

<figure><img src="/files/BzKhQMGn50jqoeNwWTXm" alt="An image showing my progress on Exercism. It&#x27;s 22.4% as of Sunday, 28 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 20

Saturday, 27 August 2022 - Day 20 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [RPN Calculator](#rpn-calculator)

### RPN Calculator

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/rpn-calculator) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/rpn-calculator/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule RPNCalculator do
  @spec calculate!(list(), Function.t()) :: :ok
  def calculate!(_stack, operation) do
    operation.(1)
  end

  @spec calculate(list(), Function.t()) :: {:ok, String.t()} | :error
  def calculate(_stack, operation) do
    try do
      operation.(1)
      {:ok, "operation completed"}
    rescue
      _ -> :error
    end
  end

  @spec calculate_verbose(list(), Function.t()) :: {:ok | :error, String.t()}
  def calculate_verbose(_stack, operation) do
    try do
      operation.(1)
      {:ok, "operation completed"}
    rescue
      e in ArgumentError -> {:error, e.message}
    end
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/7cbaacb697de6a52c1931703dd92039e)

</details>

#### Notes

I was trying to figure out how to call an anonymous function assigned to a variable when the anonymous functions has no arguments. It's `the_variable.()`. However, it turns out this exercise expects an anonymous function with an arity of 1. But the parameter is not being used. So you can pass anything really: `the_variable.(1)`.

## Overall progress

<figure><img src="/files/WGux1b1kKk4HdQyNRf0k" alt="An image showing my progress on Exercism. It&#x27;s 21.8% as of Saturday, 27 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 19

Friday, 26 August 2022 - Day 19 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [Need For Speed](#need-for-speed)

### Need For Speed

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/need-for-speed) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/need-for-speed/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule NeedForSpeed do
  alias NeedForSpeed.Race
  alias NeedForSpeed.RemoteControlCar, as: Car
  import IO, only: [puts: 1]
  import IO.ANSI, except: [color: 1]

  def print_race(%Race{} = race) do
    puts("""
    🏁 #{race.title} 🏁
    Status: #{Race.display_status(race)}
    Distance: #{Race.display_distance(race)}

    Contestants:
    """)

    race.cars
    |> Enum.sort_by(&(-1 * &1.distance_driven_in_meters))
    |> Enum.with_index()
    |> Enum.each(fn {car, index} -> print_car(car, index + 1) end)
  end

  defp print_car(%Car{} = car, index) do
    color = color(car)

    puts("""
      #{index}. #{color}#{car.nickname}#{default_color()}
      Distance: #{Car.display_distance(car)}
      Battery: #{Car.display_battery(car)}
    """)
  end

  defp color(%Car{} = car) do
    case car.color do
      :red -> red()
      :blue -> cyan()
      :green -> green()
    end
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/5d3f62baccd06ae629942defa1121964)

</details>

#### Notes

Interesting. Pretty short and fast. Learned a few things about aliases and imports. I still don't fully understand what is the difference between `alias` and `import`.

## Overall progress

<figure><img src="/files/8yUXa3vieugUoeDqoY8Q" alt="An image showing my progress on Exercism. It&#x27;s 21.2% as of Friday, 26 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 18

Thursday, 25 August 2022 - Day 18 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [Captain's Log](#captains-log)

### Captain's Log

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/captains-log) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/captains-log/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule CaptainsLog do
  @planetary_classes ["D", "H", "J", "K", "L", "M", "N", "R", "T", "Y"]

  @spec random_planet_class() :: String.t()
  def random_planet_class(), do: Enum.random(@planetary_classes)

  @spec random_ship_registry_number() :: String.t()
  def random_ship_registry_number(), do: "NCC-#{Enum.random(1000..9999)}"

  @spec random_stardate() :: float
  def random_stardate(), do: 41000.0 + (42000.0 - 41000.0) * :rand.uniform()

  @spec format_stardate(Float.t()) :: String.t()
  def format_stardate(stardate), do: :io_lib.format("~.1f", [stardate]) |> List.to_string()
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/f3ea94dce96faf70c87c38e6528d14f4)

</details>

#### Notes

Part of this exercise is showing you how to use Erlang libraries and functions for functionality that doesn't exist in Elixir. However, the Erlang documentation for those functions is not as digestible as Elixir's documentation.

## Overall progress

<figure><img src="/files/vodJjyd3tYEAjjXOaqfJ" alt="An image showing my progress on Exercism. It&#x27;s 20.5% as of Thursday, 25 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 17

Wednesday, 24 August 2022 - Day 17 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [Boutique Suggestions](#boutique-suggestions)
* [x] [Community Garden](#community-garden)
* [x] [Bread And Potions](#bread-and-potions)

### Boutique Suggestions

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/boutique-suggestions) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/boutique-suggestions/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule BoutiqueSuggestions do
  def get_combinations(tops, bottoms, options \\ []) do
    mp = Keyword.get(options, :maximum_price, 100.0)

    for x <- tops,
        y <- bottoms,
        x.base_color != y.base_color and x.price + y.price <= mp do
      {x, y}
    end
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/d6631a35a83dde4019024667c6a1a1d3)

</details>

#### Notes

### Community Garden

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/community-garden) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/community-garden/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule Plot do
  @enforce_keys [:plot_id, :registered_to]
  defstruct [:plot_id, :registered_to]
end

defmodule CommunityGarden do
  @spec start(Keyword.t()) :: tuple()
  def start(opts \\ []) do
    Agent.start(fn -> [] end, opts)
  end

  @spec list_registrations(pid()) :: list(Plot)
  def list_registrations(pid) do
    Agent.get(pid, fn state -> state end)
  end

  @spec register(pid(), String.t()) :: Plot
  def register(pid, register_to) do
    Agent.update(pid, fn state ->
      [%Plot{plot_id: get_next_id(state), registered_to: register_to} | state]
    end)

    Agent.get(pid, fn state ->
      [head | _tail] = state
      head
    end)
  end

  defp get_next_id([]), do: 1
  defp get_next_id(plots), do: Enum.sort_by(plots, & &1.plot_id)

  @spec release(pid(), integer()) :: :ok
  def release(pid, plot_id) do
    Agent.update(pid, fn state ->
      Enum.filter(state, fn plot -> plot.plot_id != plot_id end)
    end)
  end

  @spec get_registration(pid(), integer()) :: tuple() | Plot
  def get_registration(pid, plot_id) do
    plot =
      Agent.get(pid, fn state ->
        Enum.find(state, nil, fn plot -> plot.plot_id == plot_id end)
      end)

    cond do
      plot == nil -> {:not_found, "plot is unregistered"}
      true -> plot
    end
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/2476a9647535d8459a7b5f4973ec558a)

</details>

#### Notes

### Bread and Potions

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/bread-and-potions) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/bread-and-potions/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule RPG do
  defmodule Character do
    defstruct health: 100, mana: 0
  end

  defmodule LoafOfBread do
    defstruct []
  end

  defmodule ManaPotion do
    defstruct strength: 10
  end

  defmodule Poison do
    defstruct []
  end

  defmodule EmptyBottle do
    defstruct []
  end

  defprotocol Edible do
    @spec eat(t(), %RPG.Character{}) :: any()
    def eat(item, character)
  end

  defimpl Edible, for: LoafOfBread do
    @spec eat(%RPG.LoafOfBread{}, %RPG.Character{}) :: {nil, %RPG.Character{}}
    def eat(_item, character), do: {nil, %Character{character | health: character.health + 5}}
  end

  defimpl Edible, for: ManaPotion do
    @spec eat(%RPG.ManaPotion{}, %RPG.Character{}) :: {%RPG.EmptyBottle{}, %RPG.Character{}}
    def eat(item, character),
      do: {%RPG.EmptyBottle{}, %Character{character | mana: character.mana + item.strength}}
  end

  defimpl Edible, for: Poison do
    @spec eat(%RPG.Poison{}, %RPG.Character{}) :: {%RPG.EmptyBottle{}, %RPG.Character{}}
    def eat(_item, character), do: {%RPG.EmptyBottle{}, %Character{character | health: 0}}
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/2d1b7a62e01f005244eaf68971126a82)

</details>

#### Notes

## Overall progress

<figure><img src="/files/uVqtj6NU6EFTfMXU0I46" alt="An image showing my progress on Exercism. It&#x27;s 19.9% as of Wednesday, 24 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 16

Tuesday, 23 August 2022 - Day 16 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [Newsletter](#newsletter)
* [x] [Chessboard](#chessboard)
* [x] [Remote Control Car](#remote-control-car)

### Newsletter

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/newsletter) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/newsletter/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule Newsletter do
  @spec read_emails(path :: String.t()) :: list(String.t())
  def read_emails(path) do
    {:ok, contents} = File.read(path)
    String.split(contents, "\n", trim: true)
  end

  @spec open_log(path :: String.t()) :: pid()
  def open_log(path) do
    File.open!(path, [:write])
  end

  @spec log_sent_email(pid(), email :: String.t()) :: :ok
  def log_sent_email(pid, email) do
    IO.puts(pid, email)
  end

  @spec close_log(pid()) :: :ok
  def close_log(pid) do
    File.close(pid)
  end

  @spec send_newsletter(String.t(), String.t(), fun()) :: :ok
  def send_newsletter(emails_path, log_path, send_fun) do
    pid = open_log(log_path)

    read_emails(emails_path)
    |> Enum.each(fn email -> if :ok == send_fun.(email), do: log_sent_email(pid, email) end)

    close_log(pid)
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/5f009188ff3aeebbba688136c729f14b)

</details>

#### Notes

I had to use `File.open!` and I could have used `IO.puts` to avoid having to concatenate a `\n`.

### Chessboard

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/chessboard) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/chessboard/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule Chessboard do
  @spec rank_range :: Range.t()
  def rank_range, do: 1..8

  @spec file_range :: Range.t()
  def file_range, do: ?A..?H

  @spec ranks :: list(integer())
  def ranks, do: Enum.to_list(rank_range())

  @spec files :: list(String.t())
  def files, do: Enum.map(file_range(), &<<&1>>)
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/a6115fd860bd793a07c9e4552b9df0e2)

</details>

#### Notes

I really love the shorthand: `Enum.map(file_range(), &<<&1>>)`

### Remote Control Car

[Exercise on Exercism](https://exercism.org/tracks/elixir/exercises/remote-control-car) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/remote-control-car/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule RemoteControlCar do
  @enforce_keys [:nickname]
  defstruct [:nickname, battery_percentage: 100, distance_driven_in_meters: 0]

  @spec new(String.t()) :: struct()
  def new(nickname \\ "none") do
    %RemoteControlCar{nickname: nickname}
  end

  @spec display_distance(remote_car :: struct()) :: String.t()
  def display_distance(%RemoteControlCar{} = remote_car) do
    "#{remote_car.distance_driven_in_meters} meters"
  end

  @spec display_battery(remote_car :: struct()) :: String.t()
  def display_battery(%RemoteControlCar{battery_percentage: 0}), do: "Battery empty"

  def display_battery(%RemoteControlCar{} = remote_car),
    do: "Battery at #{remote_car.battery_percentage}%"

  @spec drive(remote_car :: struct()) :: struct()
  def drive(%RemoteControlCar{battery_percentage: 0} = remote_car), do: remote_car

  def drive(%RemoteControlCar{} = remote_car) do
    %{
      remote_car
      | battery_percentage: remote_car.battery_percentage - 1,
        distance_driven_in_meters: remote_car.distance_driven_in_meters + 20
    }
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/5659061fbaa406a6b55508e56e555663)

</details>

#### Notes

I didn't know about the `@enforce_keys` module attribute.

## Overall progress

<figure><img src="/files/jydruo2XP5XJN9QJjPQr" alt="An image showing my progress on Exercism. It&#x27;s 17.9% as of Tuesday, 23 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Day 15

Monday, 22 August 2022 - Day 15 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [File Sniffer](#file-sniffer)

### File Sniffer

[File Sniffer in Elixir on Exercism](https://exercism.org/tracks/elixir/exercises/file-sniffer) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/file-sniffer/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule FileSniffer do
  @exe_media_type "application/octet-stream"
  @bmp_media_type "image/bmp"
  @png_media_type "image/png"
  @jpg_media_type "image/jpg"
  @gif_media_type "image/gif"

  @spec type_from_extension(String.t()) :: String.t()
  def type_from_extension("exe"), do: @exe_media_type
  def type_from_extension("bmp"), do: @bmp_media_type
  def type_from_extension("png"), do: @png_media_type
  def type_from_extension("jpg"), do: @jpg_media_type
  def type_from_extension("gif"), do: @gif_media_type

  @exe_signature <<0x7F, 0x45, 0x4C, 0x46>>
  @bmp_signature <<0x42, 0x4D>>
  @png_signature <<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A>>
  @jpg_signature <<0xFF, 0xD8, 0xFF>>
  @gif_signature <<0x47, 0x49, 0x46>>

  @spec type_from_binary(binary()) :: String.t()
  def type_from_binary(<<@exe_signature, _::binary>>), do: @exe_media_type
  def type_from_binary(<<@bmp_signature, _::binary>>), do: @bmp_media_type
  def type_from_binary(<<@png_signature, _::binary>>), do: @png_media_type
  def type_from_binary(<<@jpg_signature, _::binary>>), do: @jpg_media_type
  def type_from_binary(<<@gif_signature, _::binary>>), do: @gif_media_type

  @spec verify(binary(), String.t()) :: {:ok | :error, String.t()}
  def verify(file_binary, extension) do
    media_type = type_from_extension(extension)

    cond do
      type_from_binary(file_binary) == media_type ->
        {:ok, media_type}

      true ->
        {:error, "Warning, file format and file extension do not match."}
    end
  end
end

```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/298441d6c37bb32a7ab9e77ecda149e3)

</details>

#### Notes

This one was not very difficult. I like how I used module attributes to avoid repeating myself with the media type.

## Overall progress

<figure><img src="/files/SEikvRb616Seo1qGJUc8" alt="An image showing my progress on Exercism. It&#x27;s 16% as of Monday, 22 August 2022."><figcaption></figcaption></figure>


# Day 14

Sunday, 21 August 2022 - Day 14 of Exhort August 2022 - Exercism (My Elixir Journey)

## Exercises

* [x] [Boutique Inventory](#boutique-inventory)

### Boutique Inventory

[Boutique Inventory in Elixir on Exercism](https://exercism.org/tracks/elixir/exercises/boutique-inventory) **|** [View my solution](https://exercism.org/tracks/elixir/exercises/boutique-inventory/solutions/petros)

#### Solution

<details>

<summary>Expand to see code (spoiler alert)</summary>

{% code lineNumbers="true" %}

```elixir
defmodule BoutiqueInventory do
  def sort_by_price(inventory) do
    inventory
    |> Enum.sort_by(&(&1.price))
  end

  def with_missing_price(inventory) do
    inventory
    |> Enum.filter(&is_nil(&1.price))
  end

  def update_names(inventory, old_word, new_word) do
    inventory
    |> Enum.map(&replace_word(&1, old_word, new_word))
  end

  defp replace_word(map, old_word, new_word) do
    name = String.replace(map.name, old_word, new_word)
    Map.put(map, :name, name)
  end

  def increase_quantity(item, count) do
    qbs = Map.new(item.quantity_by_size, fn {k, v} -> {k, v + count} end)
    %{item | quantity_by_size: qbs }
  end

  def total_quantity(item) do
    item.quantity_by_size
    |> Enum.reduce(0, fn {_, v}, sum -> sum + v end)
  end
end
```

{% endcode %}

[View gist on GitHub](https://gist.github.com/petros/809098bdb9b12a6b3def38b291e7f05e)

</details>

#### Notes

Oh Zeus. I really struggled with this one. And it was so simple. I don't know why. Here's my accepted solution:

```elixir
def increase_quantity(item, count) do
  qbs = Map.new(item.quantity_by_size, fn {k, v} -> {k, v + count} end)
  %{item | quantity_by_size: qbs }
end
```

I was trying all sorts of irrelevant things :grimacing:. I was also thrown off by the mention of `Enum.into` in `README.md`. I guess it was an alternative to `Map.new`?\
\
For `total_quantity/1`, I used a different approach, while the exercise recommended `Enum.reduce`. It was the purpose of the exercise to teach you that. My initial implementation was:

```elixir
item.quantity_by_size
|> Map.values
|> Enum.sum
```

I think it's a little easier in the eyes, but I have no idea if it's slower as I am calling a `Map` function and then an `Enum` function. Whereas `reduce/3` is one function.

## Overall progress

<figure><img src="/files/UQqPBFzpi31rjQ2mdvsx" alt="An image showing my progress on Exercism. It&#x27;s 15.4% as of Sunday, 21 August 2022."><figcaption><p>Progress</p></figcaption></figure>


# Bits and pieces

Knowledge bits and pieces


# Clean mix dependencies

Clean mix dependencies

Every once in a while, or when you have removed a dependency from your project, or you have run `mix deps.update`, it is a good practice to clean unused dependencies.

This is more to free disk space.

Here's how to do it:

```shell
mix deps.clean --unlock --unused
```

{% hint style="info" %}
Do you have feedback or questions about this? [Email me](mailto:petros@hey.com).
{% endhint %}


# Run tests automatically on save

Running your tests automatically every time you save helps a lot with languages like Elixir.

Reported from [dev.to](https://dev.to/petros/run-tests-automatically-on-save-1bcm)

I was looking for a solution to run tests automatically every time I save any changes. The best way so far for me is the following [hex](https://hex.pm/) package:

[mix\_test\_watch](https://hex.pm/packages/mix_test_watch)

### Install the dependency

```elixir
# mix.exs (v1.13)
def deps do
  [
    {:mix_test_watch, "~> 1.0", only: :dev}
  ]
end
```

### Configure it in your project

```elixir
# config/config.exs
import Config
​
if config_env() == :dev do
  config :mix_test_watch,
    clear: true
end
```

The `clear: true` option means that the screen will clear every time tests run. This is useful because it is easier to scroll back to the top of the most recent test run.

### Start watching for changes

In your terminal or within a VS Code terminal, this works great:

```shell
mix test.watch --seed 0 --max-failures 1 --include pending
```

### Example

Here's an example of how this looks in VS Code:

<figure><img src="/files/6HWsBrPTbacGdOltxAhW" alt=""><figcaption><p>Saving a file in VS Code, runs the tests automatically in the terminal below.</p></figcaption></figure>

Enjoy!

{% hint style="info" %}
Do you have feedback or questions about this? [Email me](mailto:petros@hey.com).
{% endhint %}


# Run tests and stop on first failure

Run tests and stop on first failure

Here's how to run all tests in the same order as in the file and stop after the first failure:

```shell
mix test --seed 0 --max-failures 1
```

{% hint style="info" %}
Do you have feedback or questions about this? [Email me](mailto:petros@hey.com).
{% endhint %}


# How to remove Tailwind from a Phoenix project

There's a chance you have an existing Phoenix project that has been created with the default option of having TailwindCSS integrated, but you would like to go vanilla CSS.

## Steps

1. Remove `:tailwind` from `mix.exs`
2. Remove related commands from `aliases` in  `mix.exs` (make sure to preserve any `esbuild` builds if you are using that).
3. Remove `@import "tailwindcss/...` from `app.css`
4. Open `js/app.js` and add `import "../css/app.css"` to it.
5. Delete file `/assets/tailwind.config.js`
6. Remove block starting with   `config :tailwind` from `config.exs`
7. Remove `tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}` from `watchers` inside `dev.exs`
8. Run `mix deps.clean --unlock --unused`

{% hint style="info" %}
You may have to fix layout related issues, how you render flash messages, and adjust `core_components.exs.`

`Original solution found in` [`Elixir Forums`](https://elixirforum.com/t/how-to-remove-tailwind-from-phoenix-completely-i-only-want-vanilla-css/54551)`.`
{% endhint %}


