# Day 21

## 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="https://722931016-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FDUq5E45vIeUDn4lY3Psn%2Fuploads%2FBXwbZk84WaCj6e2YNb9z%2F20220828-exhort-day21-progress.png?alt=media&#x26;token=147cbcf7-ead9-4718-a61a-a27325e3617b" 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>
