Fungsi
Di Elixir dan banyak bahasa fungsional lainnya, fungsi adalah first class citizen. Kita akan pelajari tentang tipe-tipe fungsi di Elixir, apa yang membuatnya berbeda, dan bagaimana menggunakannya.
Anonymous function
Seperti dimengerti dari namanya, fungsi anonim tidak bernama. Seperti kita lihat di pelajaran Enum
, fungsi semacam ini seringkali digunakan sebagai parameter ke fungsi lain. Untuk membuat sebuah fungsi anonim di Elixir kita menggunakan kata kunci fn
dan end
. Di antara keduanya kita dapat mendefinisikan sejumlah parameter dan tubuh fungsi dengan dipisahkan dengan ->
.
Mari lihat sebuah contoh mendasar:
iex> sum = fn (a, b) -> a + b end
iex> sum.(2, 3)
5
The & shorthand
Penggunaan fungsi anonim adalah praktek yang sangat umum sehingga ada singkatan untuk melakukannya:
iex> sum = &(&1 + &2)
iex> sum.(2, 3)
5
Sebagai yang bisa diduga, dalam versi singkat, parameter kita bisa diakses sebagai &1
, &2
, &3
, dan seterusnya.
Pencocokan pola
Pencocokan pola (pattern matching) tidak terbatas pada hanya variabel di Elixir. Ia bisa juga diterapkan pada penanda fungsi (function signature) seperti dapat kita lihat dalam bagian ini.
Elixir menggunakan pencocokan pola untuk mengidentifikasikan kumpulan pertama parameter yang cocok dan menjalankan tubuh fungsi yang bersesuaian:
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!
Fungsi bernama
Kita dapat mendefinisikan fungsi yang memiliki nama sehingga kita dapat merujuk padanya, fungsi seperti ini didefinisikan dengan kata kunci def
di dalam sebuah modul. Kita akan pelajari lebih jauh tentan Modul di pelajaran-pelajaran berikutnya, untuk saat ini kita akan fokus pada fungsi bernama saja.
Fungsi yang didefinisikan dalam sebuah modul dapat digunakan oleh modul lain, ini adalah fitur penting Elixir dalam pembuatan aplikasi:
defmodule Greeter do
def hello(name) do
"Hello, " <> name
end
end
iex> Greeter.hello("Sean")
"Hello, Sean"
Jika tubuh fungsi yang kita buat hanya terdiri dari satu baris, kita dapat menyingkatnya dengan do:
:
defmodule Greeter do
def hello(name), do: "Hello, " <> name
end
Dilengkapi dengan pengetahuan kita tentang pencocokan pola, mari kita eksplorasi rekursi menggunakan fungsi bernama:
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
Fungsi privat
Jika kita tidak ingin modul lain mengakses sebuah fungsi, kita dapat menggunakan fungsi privat yang hanya bisa dipanggil di dalam Module mereka. Kita dapat mendefinisikannya dengan 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()
Guard
Kita sudah sekilas menyinggung guard di pelajaran Struktur Kendali, sekarang kita akan melihat bagaimana menerapkannya dalam fungsi bernama. Saat Elixir sudah menemukan fungsi yang cocok, guard yang ada akan diuji.
Dalam contoh berikut kita memiliki dua fungsi yang memiliki penanda (signature) yang sama, kita bergantung pada guard untuk menentukan mana yang akan digunakan berdasarkan tipe argumen/parameternya:
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"
Default arguments
Jika kita inginkan adanya nilai default untuk salah satu argumen, kita gunakan sintaks argument \\ value
:
defmodule Greeter do
def hello(name, language_code \\ "en") do
phrase(language_code) <> name
end
defp phrase("en"), do: "Hello, "
defp phrase("es"), do: "Hola, "
end
iex> Greeter.hello("Sean", "en")
"Hello, Sean"
iex> Greeter.hello("Sean")
"Hello, Sean"
iex> Greeter.hello("Sean", "es")
"Hola, Sean"
Ketika kita menggabungkan contoh guard kita dengan argumen default, kita bertemu sebuah masalah. Mari kita lihat seperti apa:
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 tidak suka dengan argumen default dalam fungsi yang tercocok rangkap (multiple matching), terlalu membingungkan. Untuk mengatasi hal ini kita menambahkan sebuah kepala fungsi (function head) dengan argumen default kita:
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!