Elixir: use, import ou require, qual a diferença?
Tags: elixir, ptbr
Published: 2020-10-05 03:36:42

Não sei você, mas quando comecei a ir a fundo em Elixir, acabei descobrindo que não entendia corretamente o que algumas palavras-chave faziam.

Essas dúvidas apareceram principalmente após eu começar a estudar e usar o framework Phoenix em projetos pessoais -- todos projetos do tipo *throw away*. Mesmo assim, me incomodava o fato de não entender o que o framework estava tentando me dizer, parecia que existia algum tipo de *mágica* desnecessária lá. E eu não gosto de mágica dentro do código.

Se você está passando pelo mesmo caso, talvez esse texto te ajude. Se você está apenas de curioso lendo, que massa! Espero que você aprenda pelo menos alguma coisa nesse texto.

## Diretiva `import`

Tudo começa com a definição do schema de algum dado no banco de dados usando o Ecto. Se você por acaso não sabe, o Phoenix usa, por padrão, o Ecto como biblioteca para trabalhar com a persistência de dados. Quando escrevemos (ou geramos) o esquema de alguma tabela, o arquivo geralmente tem essa cara:

```elixir
defmodule Project.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset # << O que essa linha faz?

  schema "users" do
    field :email, :string
    field :name, :string
    field :password, :string

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :password])
    |> validate_required([:name, :email, :password])
  end
end
```

No exemplo acima, a macro `schema` define os campos de um determinado dado que deverá ser persistido na tabela `users`. Não quero entrar em detalhes sobre o Ecto nesse artigo, então caso você não entenda o que o código acima faz, imagine que o trecho abaixo define as colunas e os tipos dos dados armazenados em cada coluna da tabela `users`:

```elixir
  schema "users" do
    field :email, :string
    field :name, :string
    field :password, :string

    timestamps()
  end
```

Entretanto, sobre a perspectiva da diretiva `import`, esse trecho é irrelevante. Concentre-se em:

```elixir
  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :email, :password])
    |> validate_required([:name, :email, :password])
  end
```

O módulo `Project.Accounts.User` define uma função chamada `changeset` que usa outras duas funções que não estão definidas neste módulo: `cast` e `validate_required`. De onde essas funções vêm?

A palavra-chave `import` funciona como um indicativo ao compilador: *ei, quando você compilar o módulo **`Project.Accounts.User`**, inclua todas as funções e macros definidas no módulo **`Ecto.Changeset`**, assim o código do módulo `User` vai poder usar essas funções/macros*. É devido a esse comportamento que conseguimos usar as funções `cast` e `validate_required` dentro da função `changeset`.

Se você quiser um exemplo melhor, veja o seguinte arquivo que define dois módulos:

```elixir
defmodule Module1 do
  def func do
    IO.puts "rodando Module1.func"
  end

  defmacro meu_unless(clause, do: expression) do
    # fonte: https://elixir-lang.org/getting-started/meta/macros.html
    quote do
      if(!unquote(clause), do: unquote(expression))
    end
  end
end

defmodule Mods do
  import Module1

  def run do
    IO.puts("rodando!")
    func()
    meu_unless false, do: IO.puts "rodei"
  end
end
```

No módulo `Mods` foram importadas funções e macros de `Module1` e ambos são usados na definição da função `Mods.run/1`. Ah, a diretiva também possui o mesmo comportamento quando usada dentro do corpo de uma função! Se a linha `import Module1` for movida para dentro da definição de `run`, o código ainda vai funcionar. Por exemplo:

```elixir
defmodule Mods do
  def run do
    import Module1
    IO.puts("rodando!")
    func()
    meu_unless false, do: IO.puts "rodei"
  end
end
```

Então, resumindo: *a diretiva `import` importa todas as funções e macros de um **módulo** para o contexto onde a diretiva `import` está sendo usada*.

## Diretiva `require`

Para a sua surpresa, a diretiva `require` faz o mesmo que a diretiva `import`, entretanto não importa funções! Se usarmos o mesmo exemplo anterior e trocarmos a palavra `import` por `require`, dessa forma:

```elixir 
defmodule Module1 do
  def func do
    IO.puts "rodando Module1.func"
  end

  defmacro meu_unless(clause, do: expression) do
    quote do
      if(!unquote(clause), do: unquote(expression))
    end
  end
end

defmodule Mods do
  require Module1 # <<

  def run do
    IO.puts("rodando!")
    func()
    meu_unless false, do: IO.puts "rodei"
  end
end
```

Você verá que o compilador do Elixir vai informar o seguinte erro:

```
== Compilation error in file lib/mods.ex ==
** (CompileError) lib/mods.ex:18: undefined function func/0
    (elixir 1.10.4) src/elixir_locals.erl:114: anonymous fn/3 in :elixir_locals.ensure_no_undefined_local/3    
    (stdlib 3.8) erl_eval.erl:680: :erl_eval.do_apply/6
```

Meio confuso né? Mas é basicamente o Elixir tentando nos dizer que a função `func` não está definida dentro do contexto onde é invocada.

## Diretiva `use`

Voltando para o primeiro trecho de código que foi mostrado nesse artigo, o que a linha `use Ecto.Schema` faz? Imagine que o `use` consegue de alguma forma injetar um *comportamento* dentro do contexto.

O que é um comportamento? Como funciona essa "injeção"? Veja a definição do módulo `Ecto.Schema` em [https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/schema.ex](https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/schema.ex). Pelo menos na branch master no momento que escrevo esse artigo (noite do dia 4/out/2020), na linha 450 começa a definição de uma macro chamada `__using__`. Por quê essa macro é importante?

A diretiva `use` injeta um comportamento no módulo/contexto onde é usada permitindo que o compilador do Elixir insira, edite ou remova código definido dentro do contexto corrente. Lembre-se que trabalhar com macros é como trabalhar com o compilador. Você não está escrevendo código para ser executado em tempo de execução (como todo código que você provavelmente escreve hoje), mas sim, escreve código para ser "executado" durante a compilação.

Segundo a [documentação](https://elixir-lang.org/getting-started/alias-require-and-import.html#use) no site oficial do Elixir, o módulo `Example` que usa um módulo `Feature`, permite que `Feature` opere sobre o contexto de `Example`:

```elixir
defmodule Feature do
  defmacro __using__(_) do
    quote do
      IO.puts "Feature.__using__/1"

      def some_func do
        IO.puts "oops"
      end
    end
  end
end

defmodule Example do
  use Feature
end
```

Quando compilado, o módulo `Example` terá a seguinte cara:

```elixir
defmodule Example do
  require Feature
  Feature.__using__()
end
```

O que ganhamos com isso? A função `__using__` define uma nova função `some_func`. Essa função será injetada dentro do contexto onde o `use` está sendo usado. Por exemplo:

```elixir
defmodule Example do
  use Feature

  def run do
    some_func()
  end
end
```

Quando executada a função `Example.run/1`, o seguinte resultado será mostrado no terminal:

```
Feature.__using__/1
oops 
```

## Conclusão

Propositalmente escolhi não entrar muito em detalhes nesse texto justamente porque o objetivo era entender a diferença entre `use`, `import` e `require` já que essas três palavras aparecem várias vezes em códigos Elixir, principalmente se você estiver usando Phoenix e Ecto. 

Existe um mundo a parte só envolvendo o sistema de macros de Elixir, conseguimos desde injetar métodos em contextos como também estender a linguagem como bem entendermos.

O Ecto é uma prova viva. A biblioteca implementa praticamente uma linguagem dentro de Elixir para conseguir lidar de forma eficiente com queries no banco de dados.

## Para saber mais

Algumas fontes que eu usei para responder a questão que envolveu a escrita desse texto, talvez seja interessante a leitura:

* [Programming Elixir 1.6](https://pragprog.com/titles/elixir16/programming-elixir-1-6/)
* [https://github.com/elixir-lang/elixir](https://github.com/elixir-lang/elixir)
* [https://github.com/elixir-ecto/ecto](https://github.com/elixir-ecto/ecto)