Do you want to pick up from where you left of?
Take me there

Moduler

Vi vet fra erfaring at det er besværlig å ha alle våre funksjoner i samme fil og skop. I denne leksjonen skal vi dekke hvordan vi grupperer funksjoner, og definerer en spesialisert form for map kjent som struct for å kunne organiserer koden vår mer effektivt.

Moduler

Moduler er den beste måten å organisere funksjoner inn i egne navnområder. I tillegg til å gruppere funksjoner, tillater det oss å definere våre egne navnkalte og private funksjoner som vi gikk igjennom i forrige leksjon.

La oss se på et enkelt eksempel:

defmodule Example do
  def greeting(name) do
    "Hello #{name}."
  end
end

iex> Example.greeting "Sean"
"Hello Sean."

I Elixir så er det mulig å nøste moduler i hverandre:

defmodule Example.Greetings do
  def morning(name) do
    "Good morning #{name}."
  end

  def evening(name) do
    "Good night #{name}."
  end
end

iex> Example.Greetings.morning "Sean"
"Good morning Sean."

Modul Attributer

Modul attributer er som oftest brukt som konstanter i Elixir. La oss se på et enkelt eksempel:

defmodule Example do
  @greeting "Hello"

  def greeting(name) do
    ~s(#{@greeting} #{name}.)
  end
end

Det er viktig å notere at det er reserverte attributer i Elixir. De tre vanligste er:

Structs

Structs er en spesielle form for map, de definerer en mengde av nøkler og standard verdier. En struct må være definert i en egen modul, som den også får navnet sitt fra. Det er normalt at en struct er det eneste som er definert i en modul.

For å definere en struct bruker vi defstruct sammen med en nøkkelord liste av felt og standard verdier.

defmodule Example.User do
  defstruct name: "Sean", roles: []
end

La oss lage noen structs:

iex> %Example.User{}
%Example.User<name: "Sean", roles: [], ...>

iex> %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [], ...>

iex> %Example.User{name: "Steve", roles: [:manager]}
%Example.User<name: "Steve", roles: [:manager]>

Vi kan oppdatere vår struct akkurat som med en map:

iex> steve = %Example.User{name: "Steve"}
%Example.User<name: "Steve", roles: [...], ...>
iex> sean = %{steve | name: "Sean"}
%Example.User<name: "Sean", roles: [...], ...>

Det viktigste er at du kan matche structs mot maps:

iex> %{navn: "Sean"} = sean
%Eksempel.Bruker{navn: "Sean", roller: [:admin, :eier]}

Komposisjon

Nå som vi vet hvordan vi lager moduler og structs, la oss lære oss hvordan vi kan legge til eksisterende funksjonalitet til de via komposisjon. Elixir forsyner oss med mange forskjellige måter å integrere med andre moduler.

alias

Tillater oss å gi alias til modulnavn; noe som er veldig ofte brukt i Elixir.

defmodule Sayings.Greetings do
  def basic(name), do: "Hi, #{name}"
end

defmodule Example do
  alias Sayings.Greetings

  def greeting(name), do: Greetings.basic(name)
end

# Uten alias

defmodule Example do
  def greeting(name), do: Sayings.Greetings.basic(name)
end

Hvis det oppstår en konflikt mellom to alias, eller om vi ønsker å bruke et alias til noe annet, så kan vi bruke :as:

defmodule Example do
  alias Sayings.Greetings, as: Hi

  def print_message(name), do: Hi.basic(name)
end

Det er også mulig å bruke et alias på flere moduler i en tilordning:

defmodule Example do
  alias Sayings.{Greetings, Farewells}
end

import

Hvis vi har lyst til å importere funksjoner og makroer istedenfor å bruke et alias for modulen kan vi bruke ìmport/:

iex> last([1, 2, 3])
** (CompileError) iex:9: undefined function last/1
iex> import List
nil
iex> last([1, 2, 3])
3

Filtrering

Per standard vil alle funksjoner og makroer bli importert, men vi kan filtrere dem ved å bruke :only og :except.

For å importere spesifikke funksjoner og makroer, må vi bruke navn/aritet paret med :only og :except. La oss starte med å kun importere last/1 funksjonen:

iex> import List, only: [last: 1]
iex> first([1, 2, 3])
** (CompileError) iex:13: undefined function first/1
iex> last([1, 2, 3])
3

Hvis vi importerer alt untatt last/1 og prøver med den samme funksjonen som tidligere:

iex> import List, except: [last: 1]
nil
iex> first([1, 2, 3])
1
iex> last([1, 2, 3])
** (CompileError) iex:3: undefined function last/1

I tillegg til navn/aritet paret så er det to spesielle atomer, :functions og :macros, som importerer kun de funksjonene eller makroene:

import List, only: :functions
import List, only: :macros

require

Brukt sjeldnere, men fortsatt viktig er require/2. Å kreve en modul forsikrer at den er kompilert og lastet. Dette er mest brukbart når vi trenger å aksessere en modul sine makroer:

defmodule Eksempel do
  require SuperMacros

  SuperMacros.do_stuff
end

Hvis vi prøver å kalle en makro som ikke er lastet, så vil Elixir heve en feil.

use

use makroen kaller en spesiell makro, kalt __using__/1, i den spesifiserte modulen. Her er et eksempel:

# lib/use_import_require/use_me.ex
defmodule UseImportRequire.UseMe do
  defmacro __using__(_) do
    quote do
      def use_test do
        IO.puts("use_test")
      end
    end
  end
end

og vi legger denne linjen til UseImportRequire:

use UseImportRequire.UseMe

Å bruke UseImportRequire.UseMe definerer en usetest/0 funksjon gjennom invokasjon av `_using/1` makroen.

Det er alt det use gjør. Men, det er vanlig at __using__ makroen kaller på alias, require eller import. Dette vil lage aliaser eller importere det som kreves i using modulen. Dette tillater modulen til å bli brukt til å definere en retningslinje for hvordan deres funksjoner og makroer burde bli referert til. Dette kan være svært fleksibelt i det at __using__/1 kan sette opp referanser til andre moduler, spesielt submoduler.

Phoenix rammeverket bruker use og __using__/1 for å kutte ned bruken av repetitive alias og import kall i bruker definerte moduler.

Her er et godt eksempel fra Ecto.Migration modulen:

defmacro __using__(_) do
  quote location: :keep do
    import Ecto.Migration
    @disable_ddl_transaction false
    @before_compile Ecto.Migration
  end
end

Ecto.Migration.__using__/1 makroen inkluderer et importkall slik at når du bruker use Ecto.Migration så vil den også kalle import Ecto.Migration. Den setter også opp modul attributer som vi kan anta kontrollerer Ecto sin atferd.

For å oppsumere: use invokerer __using__/1 i den spesifiserte modulen. For å virkelig forstå hva den gjør så anbefales det at du leser om __using__/1 makroen.

Har du oppdaget en feil eller ønsker å bidra til leksjonen? Rediger denne leksjonen på GitHub!