Atribut modul menyimpan sebuah nilai pada modul dan menyerahkannya kepada compiler pada saat kompilasi, bukan saat program berjalan. Nilai itu kemudian dibekukan ke dalam kode hasil kompilasi, sehingga perubahannya selalu menuntut kompilasi ulang. Topik ini membahas atribut kustom beserta pembekuannya, anotasi dokumentasi @moduledoc dan @doc, callback @after_compile, pendaftaran atribut dengan Module.register_attribute/3, serta atribut khusus @behaviour.
Atribut dibaca pada saat kompilasi#
Atribut modul ditulis dengan tanda @ diikuti namanya, misalnya @kurs 15_000. Penugasan berikutnya dapat mengganti nilainya, dan fungsi yang membaca atribut itu membekukan nilai yang berlaku pada baris definisi fungsinya. Karena itu, dua fungsi dalam satu modul dapat membawa dua nilai berbeda dari atribut yang sama, bergantung pada urutan penugasannya.
Contoh berikut mengganti @kurs di tengah modul. Fungsi kurs_awal/0 didefinisikan sebelum penggantian, sedangkan kurs_revisi/0 sesudahnya:
defmodule KursDuaWaktu do
@kurs 15_000
def kurs_awal, do: @kurs
@kurs 15_100
def kurs_revisi, do: @kurs
end
IO.inspect(KursDuaWaktu.kurs_awal(), label: "kurs awal")
IO.inspect(KursDuaWaktu.kurs_revisi(), label: "kurs revisi")kurs awal: 15000
kurs revisi: 15100Kedua fungsi mengembalikan nilai yang berbeda meskipun bersumber dari atribut yang sama. Tidak ada mekanisme pembacaan ulang pada saat program berjalan, karena @kurs 15_000 sudah menjadi literal 15000 di dalam kurs_awal/0 begitu modul selesai dikompilasi. Nilai yang perlu berubah saat program berjalan sebaiknya disimpan melalui konfigurasi atau state process, bukan melalui atribut.
Lingkup atribut adalah modul tempatnya dituliskan. Atribut dapat dibaca di mana saja di dalam badan modul, termasuk di dalam isi fungsi, dan pembacaannya selalu merujuk nilai yang berlaku pada baris definisi fungsi itu. Membaca atribut yang belum pernah ditugaskan memicu warning kompilasi, sehingga urutan penugasan perlu dijaga dari atas ke bawah.
Skrip utuh: konstanta fisika#
Skrip berikut menggunakan atribut untuk menyimpan konstanta gravitasi. Dokumentasi modul dan fungsi dianotasi dengan @moduledoc dan @doc, nilai @gravitasi dibekukan ke dalam dua fungsi, dan @after_compile mendaftarkan fungsi yang dijalankan compiler tepat setelah modul selesai dikompilasi. Pesan dari fungsi itu muncul paling awal pada keluaran karena berjalan pada tahap kompilasi, sebelum baris program mana pun dieksekusi:
# konstanta.exs — atribut module: dibaca saat kompilasi, dibekukan ke dalam kode
defmodule KonstantaFisika do
@moduledoc """
Kumpulan konstanta fisika yang nilainya dibekukan saat kompilasi.
"""
@after_compile {__MODULE__, :catat_kompilasi}
def catat_kompilasi(_env, _bytecode) do
IO.puts("KonstantaFisika selesai dikompilasi")
end
@gravitasi 9.8
@doc "Percepatan gravitasi bumi dalam meter per detik kuadrat."
def gravitasi, do: @gravitasi
@doc "Kecepatan benda jatuh bebas setelah durasi tertentu, dalam meter per detik."
def kecepatan_jatuh(durasi), do: @gravitasi * durasi
end
IO.inspect(KonstantaFisika.gravitasi(), label: "gravitasi")
IO.inspect(KonstantaFisika.kecepatan_jatuh(2), label: "kecepatan setelah 2 detik")KonstantaFisika selesai dikompilasi
gravitasi: 9.8
kecepatan setelah 2 detik: 19.6Skripnya dapat dijalankan langsung dari terminal setelah berkasnya disimpan:
# menjalankan skrip konstanta dari terminal
elixir konstanta.exsKonstantaFisika selesai dikompilasi
gravitasi: 9.8
kecepatan setelah 2 detik: 19.6Baris pertama keluaran membuktikan bahwa @after_compile dieksekusi pada tahap kompilasi, yakni sebelum baris IO.inspect mana pun berjalan. Nilai 19.6 pada baris ketiga berasal dari @gravitasi * durasi yang mengalikan durasi dengan konstanta yang sudah dibekukan. Anotasi @moduledoc dan @doc tidak menampilkan apa pun pada keluaran, karena keduanya hanya menyimpan teks dokumentasi untuk dibaca alat seperti helper h/1 pada IEx dan ExDoc dalam proyek Mix.
Atribut tepat digunakan untuk nilai yang memang tidak pernah berubah selama program hidup, seperti konstanta fisika, batas ukuran, atau nama kanal. Anotasi semacam @moduledoc dan @doc masuk kategori yang sama, yaitu data yang dituliskan oleh penulis kode dan dibaca alat pada saat kompilasi. Sebaliknya, nilai yang berubah dari satu pemanggilan ke pemanggilan lainnya tidak pernah menjadi urusan atribut.
Pendaftaran atribut dengan Module.register_attribute/3#
Secara bawaan, penugasan atribut yang berulang menggantikan nilai sebelumnya. Module.register_attribute/3 mengubah perilaku itu: opsi accumulate: true mengumpulkan setiap penugasan menjadi sebuah list, sedangkan opsi persist: true menyimpan nilai atribut ke berkas .beam sehingga masih terbaca setelah kompilasi. Pendaftaran dilakukan sekali di awal badan modul, sebelum penugasan pertama.
Contoh berikut mendaftarkan atribut label dengan accumulate: true, lalu menugaskannya dua kali. Nilai yang terkumpul berupa list dengan penugasan terbaru di posisi depan:
defmodule ArsipKurs do
Module.register_attribute(__MODULE__, :label, accumulate: true)
@label "kurs awal 15.000"
@label "kurs revisi 15.100"
def label, do: @label
end
IO.inspect(ArsipKurs.label(), label: "nilai terkumpul")nilai terkumpul: ["kurs revisi 15.100", "kurs awal 15.000"]Pola akumulasi ini lazim digunakan untuk mendaftarkan sesuatu selama kompilasi, misalnya kumpulan pengaturan atau daftar modul yang perlu diproses lebih lanjut. Pola yang sama digunakan atribut khusus seperti @optional_callbacks pada topik behaviour, yang nilainya juga terkumpul dari beberapa penugasan.
Opsi persist: true sebaiknya digunakan secara hemat. Atribut yang disimpan pada berkas .beam ikut terbawa ke setiap lingkungan yang memuat modul tersebut, sehingga isinya tidak lagi dapat dijaga. Kebutuhan semacam penanda versi atau nama build cocok untuk atribut persist, sedangkan data sensitif tetap ditempatkan pada konfigurasi runtime.
@behaviour sebagai atribut khusus#
Sebagian atribut memiliki makna khusus yang diproses compiler, dan @behaviour salah satunya. Atribut ini mendaftarkan behaviour yang diikuti modul, sehingga compiler dapat memeriksa kelengkapan callbacknya. Pemeriksaannya berupa warning kompilasi sebagaimana dijelaskan pada topik behaviour, dan kehadiran atribut ini mengikat modul penyedia pada kontrak yang sama. Daftar behaviour yang tercatat dapat dibaca kembali dari atribut modul melalui module_info(:attributes):
defmodule KontrakUkur do
@callback ukur :: integer
end
defmodule Pengukur do
@behaviour KontrakUkur
@impl KontrakUkur
def ukur, do: 42
end
IO.inspect(Pengukur.module_info(:attributes)[:behaviour], label: "behaviour terdaftar")
IO.inspect(Pengukur.ukur(), label: "hasil ukur")behaviour terdaftar: [KontrakUkur]
hasil ukur: 42Keluaran menunjukkan KontrakUkur terdaftar sebagai behaviour milik Pengukur. Atribut khusus lainnya bekerja dengan prinsip yang sama, yaitu dianotasi pada modul lalu diproses compiler: @impl menandai implementasi callback, @spec menuliskan kontrak tipe, sedangkan @moduledoc dan @doc menyimpan dokumentasi. @before_compile tersedia sebagai pasangan dari @after_compile, yaitu menjalankan fungsi tepat sebelum modul selesai dikompilasi. Seluruhnya selesai diperhitungkan pada saat kompilasi, sehingga tidak ada biaya pembacaan atribut pada saat program berjalan.
Prinsip yang sama berlaku ketika modul penyedia ditulis dengan use: atribut @behaviour itulah yang disuntikkan ke dalam modul penggunanya, sehingga pemeriksaan callbacknya tetap berjalan. Keterkaitan atribut dan kontrak ini membuat behaviour dapat dikenali dari hasil kompilasi modul, bukan hanya dari kode sumbernya.
Ringkasan#
- Atribut modul ditugaskan dengan
@nama nilaidan dibaca pada saat kompilasi, sehingga fungsi yang membacanya membekukan nilai yang berlaku pada baris definisi fungsinya. - Penugasan berulang menggantikan nilai sebelumnya, sedangkan
Module.register_attribute/3dengan opsiaccumulate: truemengumpulkannya menjadi list. @moduledocdan@docmenyimpan dokumentasi,@after_compilemenjalankan fungsi setelah modul selesai dikompilasi, dan@behaviourmendaftarkan kontrak yang diikuti modul.- Nilai yang perlu berubah saat program berjalan bukan urusan atribut, melainkan konfigurasi atau state process.
Referensi#
- Modul Module — Hexdocs Elixir
— dokumentasi atribut modul,
register_attribute/3, dan daftar atribut khusus. - Module attributes — Hexdocs Elixir — panduan resmi atribut modul beserta contoh penggunaannya.