Funkcie
V Elixire, tak ako iných funkcionálnych jazykoch, sú funkcie základným konceptom. Povieme si o rôznych typoch funkcií v Elixire, rozdiely medzi nimi a ako ich použiť.
Anonymné funkcie
Ako naznačuje už ich názov, tieto funkcie nemajú meno. V kapitole o Enum
sme videli, že sa často odovzdávajú ako argumenty iným funkciám. Na definovanie anonymnej funkcie slúžia v Elixire kľúčové slová fn
a end
. Medzi nimi môžeme definovať ľubovoľné množstvo kombinácii argumentov a tiel funkcií - oddelených operátorom ->
.
Pozrime sa na jednoduchý príklad:
iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5
Všimnite si, že anonymnú funkciu je nutné volať cez .
.
Skratka &
Používanie anonymných funkcií je v Elixire natoľko bežné, že na ich definovanie existuje skrátený zápis pomocou &
:
iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5
Ako ste asi uhádli, v skrátenom zápise máme k dodaným argumentom prístup cez &1
, &2
, &3
atď.
Pattern matching
V Elixire nie je pattern matching obmedzený len na premenné - môže byť využitý aj v hlavičkách funkcií. Jeho aplikáciou na vstupné argumenty sa určí, ktoré telo funkcie sa použije (to, ktoré prislúcha k prvej vyhovujúcej hlavičke).
Elixir používa pattern matching na nájdenie zhodnej funkcie a zvolí prvú vyhovujúcu funkciu:
iex> handle_result = fn
...> {:ok, result} -> IO.puts "Handling result..."
...> {:error} -> IO.puts "An error has occurred!"
...> end
iex> some_result = 1
1
iex> handle_result.({:ok, some_result})
Handling result...
:ok
iex> handle_result.({:error})
An error has occurred!
V príklade sme si definovali funkciu s dvoma telami. Pri jej prvom volaní sa použilo prvé telo, keďže sme jej ako argumenty poslali tuple v tvare {:ok, result}
. Pri druhom volaní sa použilo druhé telo, keďže ako argument od nás dostala tuple v tvare {:error}
.
Pomenované funkcie
Druhým spôsobom, ako definovať funkciu, je priradiť jej už pri definícii meno, ktorým na ňu neskôr budeme odkazovať. Pri tomto spôsobe použijeme kľúčové slovo def
vo vnútri nejakého modulu (o moduloch si povieme viac v ďalšej lekcii).
Funkcie definované v module sú k dispozícii pre použitie v iných moduloch. V Elixire sú moduly jedným z najdôležitejších stavebných blokov.
defmodule Greeter do
def hello(name) do
"Hello, " <> name
end
end
iex> Greeter.hello("Sean")
"Hello, Sean"
Ak má telo funkcie len jediný riadok, môžme použiť kratší zápis definície pomocou do:
:
defmodule Greeter do
def hello(name), do: "Hello, " <> name
end
Vyzbrojení pattern matchingom, vyskúšajme si rekurziu pomocou pomenovaných funkcií:
defmodule Length do
def of([]), do: 0
def of([_ | tail]), do: 1 + of(tail)
end
iex> Length.of []
0
iex> Length.of [1, 2, 3]
3
Pomenovanie funkcií a počet argumentov
Už sme si spomenuli skôr, že funkcie sú pomenované kombináciou ich mena a počtom argumentov (arity
). To znamená, že môžeme spraviť aj niečo ako:
defmodule Greeter2 do
def hello(), do: "Hello, anonymous person!" # hello/0
def hello(name), do: "Hello, " <> name # hello/1
def hello(name1, name2), do: "Hello, #{name1} and #{name2}"
# hello/2
end
iex> Greeter2.hello()
"Hello, anonymous person!"
iex> Greeter2.hello("Fred")
"Hello, Fred"
iex> Greeter2.hello("Fred", "Jane")
"Hello, Fred and Jane"
V komentároch máme mená funkcii vyššie. Prvá implementácia nemá žiadne argumenty, tak je označená ako hello/0
. Druhá funkcia má jeden argument, takže jej názov je hello/1
atď.
Narozdiel od iných jazykov, kde by takéto niečo bolo považované za preťaženie funkcie, no v Elixire sú považované za úplne od seba rôzne funkcie. (Pattern matching, spomenutý vyššie je použitý iba vtedy, keď poskytneme viac definícií pre funkciu s rovnakým počtom argumentov.)
Privátne funkcie
Ak nechceme, aby naša funkcia mohla byť volaná z iných modulov, môžeme ju zadefinovať ako privátnu - takto ju bude možné volať len z vnútra jej vlastného modulu. Na definovanie privátnych funkcií slúži kľúčové slovo defp
:
defmodule Greeter do
def hello(name), do: phrase <> name
defp phrase, do: "Hello, "
end
iex> Greeter.hello("Sean")
"Hello, Sean"
iex> Greeter.phrase
** (UndefinedFunctionError) function Greeter.phrase/0 is undefined or private
Greeter.phrase()
Hraničné podmienky
Hraničných podmienok (guards) sme sa krátko dotkli v kapitole o riadiacich štruktúrach. Teraz sa pozrieme na ich využitie pri definovaní pomenovaných funkcií. Keď Elixir vybral funkciu, akékoľvek existujúce hraničné podmienky budú otestované.
V nasledujúcom príklade máme dve funkcie s tou istou hlavičkou, no rôznymi hraničnými podmienkami. Testujeme typ parametra:
defmodule Greeter do
def hello(names) when is_list(names) do
names = Enum.join(names, ", ")
hello(names)
end
def hello(name) when is_binary(name) do
phrase() <> name
end
defp phrase, do: "Hello, "
end
iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"
Východiskové hodnoty argumentov
Ak chceme, aby mal niektorý z argumentov funkcie východiskovú hodnotu, použijeme syntax argument \\ hodnota
:
defmodule Greeter do
def hello(name, language_code \\ "en") do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("sk"), do: "Ahoj, "
end
iex> Greeter.hello("Sean", "en")
"Hello, Sean"
iex> Greeter.hello("Sean")
"Hello, Sean"
iex> Greeter.hello("Sean", "sk")
"Ahoj, Sean"
Problém môže nastať, ak nevhodne skombinujeme hraničné podmienky s východiskovými argumentami:
defmodule Greeter do
def hello(names, language_code \\ "en") when is_list(names) do
names = Enum.join(names, ", ")
hello(names, language_code)
end
def hello(name, language_code \\ "en") when is_binary(name) do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
** (CompileError) iex:31: definitions with multiple clauses and default values require a header. Instead of:
def foo(:first_clause, b \\ :default) do ... end
def foo(:second_clause, b) do ... end
one should write:
def foo(a, b \\ :default)
def foo(:first_clause, b) do ... end
def foo(:second_clause, b) do ... end
def hello/2 has multiple clauses and defines defaults in one or more clauses
iex:31: (module)
Elixir nerád vidí východiskové argumenty vo viacerých zhodných hlavičkách funkcie, pretože to môže byť mätúce. Riešenie spočíva v pridaní hlavičky s východiskovými argumentami, pričom z pôvodných hlavičiek východiskové argumenty odstránime:
defmodule Greeter do
def hello(names, language_code \\ "en")
def hello(names, language_code) when is_list(names) do
names = Enum.join(names, ", ")
hello(names, language_code)
end
def hello(name, language_code) when is_binary(name) do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
iex> Greeter.hello ["Sean", "Steve"]
"Hello, Sean, Steve"
iex> Greeter.hello ["Sean", "Steve"], "es"
"Hola, Sean, Steve"
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!