Enum dan alias

Mendefinisikan enum dengan anggotanya serta memberi nama baru pada tipe lewat alias.

Daftar isi

Enum (enumeration) adalah tipe yang nilainya terbatas pada sejumlah anggota bernama yang didefinisikan langsung di dalam kode. Tipe ini cocok untuk status, prioritas, dan kategori yang himpunan nilainya tetap. Alias adalah nama lain untuk tipe yang sudah ada dan berguna untuk memberi nama singkat pada tipe panjang yang digunakan berulang. Topik ini membahas cara mendefinisikan enum beserta nilai anggotanya, menambahkan metode pada enum, mengubah bilangan menjadi anggota enum, serta mendefinisikan alias untuk union.

Mendefinisikan enum#

Enum didefinisikan dengan kata kunci enum, diikuti nama enum dan daftar anggotanya. Anggota ditulis dengan huruf kapital pada awalnya dan diakses melalui nama enum, misalnya Status::Aktif. Setiap anggota menyimpan sebuah bilangan yang dapat dibaca melalui value. Apabila nilai tidak ditentukan, anggota pertama bernilai 0 dan anggota berikutnya bertambah satu.

crystal status-enum.cr
enum Status
  Aktif
  Nonaktif
end

puts Status::Aktif
puts Status::Aktif.value
puts Status::Nonaktif.value
puts typeof(Status::Aktif)
puts Status.values
text Keluaran
Aktif
0
1
Status
[Status::Aktif, Status::Nonaktif]

Keluaran menunjukkan bahwa Status::Aktif dicetak sebagai nama anggotanya, nilai default kedua anggota adalah 0 dan 1, serta typeof melaporkan Status sebagai tipe anggotanya. Metode values mengembalikan seluruh anggota dalam urutan definisi, dan keluarannya menampilkan setiap anggota lengkap dengan awalan nama enum.

Menentukan nilai anggota secara eksplisit#

Nilai setiap anggota dapat ditentukan sendiri dengan tanda sama dengan setelah nama anggotanya. Nilai tersebut bebas dan tidak harus berurutan, sehingga cocok untuk kode yang nilainya sudah baku dari sistem lain. Enum juga menyertakan Comparable, sehingga anggota dapat dibandingkan dengan < dan > berdasarkan value masing-masing.

crystal prioritas-enum.cr
enum Prioritas
  Rendah = 1
  Sedang = 5
  Tinggi = 10
end

puts Prioritas::Rendah.value
puts Prioritas::Sedang.value
puts Prioritas::Tinggi.value
puts Prioritas::Rendah < Prioritas::Tinggi
puts Prioritas::Sedang > Prioritas::Rendah
text Keluaran
1
5
10
true
true

Keluaran memperlihatkan ketiga nilai yang ditentukan secara eksplisit, diikuti hasil perbandingan antaranggota. Kedua perbandingan menghasilkan true karena urutan perbandingannya mengikuti nilai anggota, yaitu 1 lebih kecil dari 10 dan 5 lebih besar dari 1. Urutan nilai inilah yang digunakan Comparable ketika < dan > dipanggil.

Metode di dalam enum#

Enum dapat memiliki metode, sama seperti tipe lainnya. Metode yang didefinisikan di dalam enum dapat membandingkan self dengan anggota lain, sehingga logika yang bergantung pada anggota enum terkumpul di satu tempat. Nama yang berakhiran tanda tanya, seperti aktif?, lazim digunakan untuk metode yang hanya mengembalikan Bool.

crystal status-metode.cr
enum Status
  Aktif
  Nonaktif

  def aktif? : Bool
    self == Status::Aktif
  end

  def label : String
    if aktif?
      "sedang berjalan"
    else
      "berhenti"
    end
  end
end

puts Status::Aktif.aktif?
puts Status::Nonaktif.aktif?
puts Status::Aktif.label
puts Status::Nonaktif.label
text Keluaran
true
false
sedang berjalan
berhenti

Metode aktif? mengembalikan true hanya untuk Status::Aktif, sedangkan label menyediakan teks tampilan untuk setiap anggota. Kedua metode dapat dipanggil pada anggota mana pun karena melekat pada enum, bukan pada salah satu anggotanya. Dengan cara ini, keputusan yang bergantung pada anggota enum tidak perlu ditulis tersebar di beberapa bagian program.

Mengubah nilai menjadi anggota enum#

Konversi dari bilangan ke anggota enum tersedia melalui from_value dan from_value?. Metode from_value mengembalikan anggota yang memiliki nilai tersebut dan melempar ArgumentError apabila tidak ada, sedangkan from_value? mengembalikan nil apabila nilainya tidak dikenal. Iterasi atas values memudahkan penelusuran seluruh anggota beserta nilainya.

crystal konversi-enum.cr
enum Prioritas
  Rendah = 1
  Sedang = 5
  Tinggi = 10
end

Prioritas.values.each do |p|
  puts "#{p} = #{p.value}"
end

puts Prioritas.from_value(5)
puts typeof(Prioritas.from_value(5))
puts Prioritas.from_value?(9)
puts Prioritas.from_value?(9).nil?

begin
  Prioritas.from_value(99)
rescue e : ArgumentError
  puts e.message
end
text Keluaran
Rendah = 1
Sedang = 5
Tinggi = 10
Sedang
Prioritas

true
Unknown enum Prioritas value: 99

Keluaran menampilkan setiap anggota dengan nilainya, hasil from_value untuk nilai 5, dan tipe kembaliannya yang tetap Prioritas. Pemanggilan from_value? untuk nilai 9 mengembalikan nil, sehingga puts mencetak satu baris kosong dan pemeriksaan nil? menghasilkan true. Pesan terakhir berasal dari blok rescue yang menangkap ArgumentError ketika from_value menerima nilai 99.

Alias sebagai nama lain tipe#

Alias dibuat dengan kata kunci alias dan memberikan nama baru untuk tipe yang sudah ada. Alias tidak menciptakan tipe berbeda. Compiler memperlakukannya persis seperti tipe aslinya, sehingga keduanya saling dipertukarkan di setiap tempat yang menerima tipe tersebut. Motivasi utama alias adalah union yang panjang dan digunakan berulang: alih-alih menulis daftar tipe yang sama berkali-kali, nama singkat didefinisikan sekali lalu digunakan pada parameter, tipe kembalian, dan tipe elemen array.

crystal alias-sandi.cr
alias KataSandi = String | Nil

akun = ["tersembunyi", nil] of KataSandi

s = akun[0]
puts typeof(s)

if s
  puts s.size
else
  puts "tanpa sandi"
end

t = akun[1]
puts typeof(t)

if t
  puts t.size
else
  puts "tanpa sandi"
end
text Keluaran
(String | Nil)
11
(String | Nil)
tanpa sandi

KataSandi didefinisikan sebagai String | Nil dan langsung digunakan sebagai tipe elemen array akun melalui klausa of. Hasil indeks array bertipe (String | Nil), sehingga pola pengecilan melalui if dapat digunakan langsung pada hasilnya. Anggota pertama berisi String, sedangkan anggota kedua berisi nil, dan kedua kasus tersebut terlihat pada keluaran.

crystal alias-nilai.cr
alias Nilai = Int32 | Float64 | String

arsip = [] of Nilai
arsip << 42
arsip << 3.5
arsip << "kristal"

puts typeof(arsip[0])

arsip.each do |n|
  if n.is_a?(String)
    puts n.upcase
  else
    puts n
  end
end
text Keluaran
(Float64 | Int32 | String)
42
3.5
KRISTAL

Nilai didefinisikan sebagai Int32 | Float64 | String, lalu digunakan sebagai tipe elemen array arsip. typeof melaporkan union yang diperluas dari alias, dan urutan anggota pada keluaran mengikuti penataan internal compiler sehingga tidak harus sama dengan urutan penulisan alias. Di dalam perulangan, cabang is_a?(String) menampung anggota bertipe String, sedangkan cabang else menampung sisanya, sehingga ketiga nilai tercetak dengan perlakuan yang sesuai.

Kesalahan umum#

Kesalahan pertama adalah membandingkan anggota enum dengan bilangan biasa. Pembandingan itu tidak ditolak compiler, tetapi selalu menghasilkan false, termasuk apabila bilangannya sama dengan value anggota tersebut:

crystal banding-enum.cr
enum Status
  Aktif
  Nonaktif
end

puts Status::Aktif == 0
puts Status::Aktif.value == 0
puts Status::Aktif == Status::Aktif
text Keluaran
false
true
true

Status::Aktif.value memang 0, tetapi Status::Aktif == 0 tetap false karena pembanding kesamaan enum hanya menerima anggota dari enum yang sama. Perbandingan antaranggota pada baris ketiga keluaran menghasilkan true karena kedua sisi pemeriksaannya bertipe sama. Perbaikannya adalah membandingkan anggota dengan anggota lain dari enum yang sama, atau membandingkan value secara eksplisit sebagaimana baris kedua pada keluaran.

Kesalahan kedua adalah menggunakan from_value untuk bilangan yang berasal dari luar program, misalnya dari berkas atau masukan pengguna. Apabila bilangannya tidak dikenal, from_value melempar ArgumentError dan program berhenti. Gunakan from_value? untuk kasus tersebut, lalu tangani nil yang dihasilkannya sebagaimana ditunjukkan pada bagian konversi di atas.

Ringkasan#

  • Enum mendefinisikan tipe dengan anggota bernama yang bernilai 0 dan seterusnya secara default, atau bernilai sesuai penentuan eksplisit, dan antaranggotanya dapat dibandingkan karena menyertakan Comparable.
  • Metode di dalam enum menampung logika yang bergantung pada anggota enum, misalnya pemeriksaan status dan teks tampilan.
  • from_value mengembalikan anggota atau melempar ArgumentError, sedangkan from_value? mengembalikan nil apabila nilai tidak dikenal.
  • Alias adalah nama lain untuk tipe yang sudah ada dan cocok untuk union panjang yang digunakan berulang.

Referensi#