Perbandingan dan iterasi adalah dua kebutuhan yang hampir selalu muncul pada kelas buatan sendiri, dan pustaka standar Ruby menyediakan keduanya dalam bentuk modul. Topik ini membahas include Comparable yang melengkapi kelas dengan operator perbandingan begitu metode <=> didefinisikan, include Enumerable yang menghidupkan puluhan metode koleksi begitu metode each tersedia, serta contoh kelas Buku yang menggunakan keduanya sekaligus. Setelah membacanya, kelas buatan sendiri dapat memperoleh kekuatan pustaka standar tanpa menulis ulang satu pun metode perbandingan atau pengolahan koleksi.
Satu metode <=> untuk enam operator#
Modul Comparable mendefinisikan seluruh operator perbandingan di atas satu metode tunggal bernama <=>, yang sering disebut spaceship operator. Metode ini membandingkan dua nilai dan menghasilkan bilangan negatif apabila penerima lebih kecil, nol apabila setara, dan bilangan positif apabila penerima lebih besar. Begitu sebuah kelas mendefinisikan <=> dan menyertakan Comparable, operator <, <=, ==, >, >=, serta metode between? langsung berfungsi tanpa didefinisikan satu per satu.
# Comparable melengkapi kelas dengan seluruh operator perbandingan begitu <=> didefinisikan.
class Suhu
include Comparable
attr_reader :derajat
def initialize(derajat)
@derajat = derajat
end
def <=>(lain)
derajat <=> lain.derajat
end
def to_s
"#{derajat} derajat"
end
end
pagi = Suhu.new(21)
siang = Suhu.new(25)
puts pagi < siang
puts pagi <= Suhu.new(21)
puts siang > pagi
puts siang >= siang
puts pagi == Suhu.new(21)
puts pagi.between?(Suhu.new(20), Suhu.new(22))
p [Suhu.new(30), Suhu.new(18), Suhu.new(24)].sort.map(&:to_s)true
true
true
true
true
true
["18 derajat", "24 derajat", "30 derajat"]Seluruh pembanding di atas bertumpu pada definisi derajat <=> lain.derajat, dan tidak ada satu pun operator yang ditulis secara manual. Baris terakhir memperlihatkan manfaat tambahannya, yaitu sort pada array memanggil <=> milik anggotanya, sehingga array berisi Suhu dapat langsung diurutkan, sedangkan to_s hanya mengubah tiap objek Suhu menjadi teks agar keluarannya mudah dibaca. Perbandingan pada between? juga mengikuti <=>, sehingga rentangnya dinyatakan dengan dua objek Suhu dan bukan dua bilangan mentah.
Dasar perbandingannya bebas dipilih sesuai makna kelasnya, yaitu derajat untuk Suhu, jumlah halaman untuk buku, atau atribut lain yang paling adil sebagai ukuran. Kontrak <=> yang sama juga dipatuhi bilangan dan string bawaan, sehingga ekspresi seperti 21 <=> 25 menghasilkan bilangan negatif dengan aturan yang persis sama. Karena seluruh operator dihitung dari satu metode, hasil perbandingan antarinstans dijamin konsisten satu sama lain, misalnya a < b dan b > a tidak mungkin berbeda selama metode <=> dirumuskan dengan benar.
Satu metode each untuk puluhan metode#
Modul Enumerable bekerja dengan pola yang sama, yaitu seluruh metodenya dibangun di atas satu metode dasar bernama each. Setiap kelas yang mampu menyampaikan elemennya satu per satu lewat each otomatis memperoleh map, select, sort, include?, min, max, dan puluhan metode lainnya. Pada pembahasan koleksi, metode-metode ini telah dipraktikkan pada Array dan Hash, dan kini asalnya terlihat, yaitu kedua kelas tersebut juga menyertakan Enumerable.
Kelas Buku pada contoh berikut menyimpan daftar jumlah halaman tiap bab dan menyertakan Enumerable sekaligus Comparable. Metode each menyampaikan jumlah halaman setiap bab satu per satu, sedangkan <=> membandingkan jumlah halaman total, sehingga buku dapat diiterasi dan dibandingkan sekaligus.
# Buku menyertakan Enumerable dan Comparable sekaligus.
class Buku
include Enumerable
include Comparable
attr_reader :judul
def initialize(judul, halaman_per_bab)
@judul = judul
@halaman_per_bab = halaman_per_bab
end
def each
@halaman_per_bab.each { |halaman| yield halaman }
end
def <=>(lain)
total_halaman <=> lain.total_halaman
end
def total_halaman
@halaman_per_bab.sum
end
end
kamus = Buku.new("Kamus Mini", [30, 40])
brosur = Buku.new("Brosur Kecil", [10, 15])
panduan = Buku.new("Panduan Cuaca", [25, 20, 30])
puts kamus < panduan
puts kamus.between?(brosur, panduan)
p panduan.map { |halaman| halaman * 2 }
p panduan.select { |halaman| halaman >= 25 }
p panduan.sort
puts panduan.include?(20)
puts panduan.min
puts panduan.max
p [panduan, kamus, brosur].sort.map(&:judul)true
true
[50, 40, 60]
[25, 30]
[20, 25, 30]
true
20
30
["Brosur Kecil", "Kamus Mini", "Panduan Cuaca"]Enam pemanggilan setelahnya, yaitu map, select, sort, include?, min, dan max, memanfaatkan each, dan tidak ada satu pun yang didefinisikan di kelas Buku. Metode map mengumpulkan hasil transformasi tiap halaman, select menyaring halaman yang memenuhi syarat, sort mengurutkan, include? memeriksa keanggotaan, sedangkan min dan max mencari kedua ujungnya. Enumerable menghitung semuanya dari aliran yang disampaikan each, sehingga satu definisi each mengaktifkan seluruh fasilitas tersebut.
Metode-metode Enumerable mengembalikan array baru dan membiarkan kelas penerimanya utuh, sehingga hasil panduan.select berupa array dan bukan objek Buku. each juga dijalankan ulang pada setiap pemanggilan metode Enumerable tanpa menyimpan hasil di antaranya, sehingga perubahan data sumber pada program yang dinamis selalu terbaca pada pemanggilan berikutnya. Baris terakhir menunjukkan kedua modul bekerja bersama, karena sort pada array berisi objek Buku memanggil <=> milik Buku, dan urutannya mengikuti jumlah halaman total, yaitu Brosur Kecil, Kamus Mini, lalu Panduan Cuaca. Tanpa Comparable, array itu tidak dapat diurutkan, dan tanpa Enumerable, metode map beserta metode-metode lainnya tidak tersedia pada Buku. Kelas buatan sendiri seperti Buku meniru pola pustaka standar, karena Range, Set, dan banyak kelas lain juga dibangun di atas Enumerable.
Kesalahan umum#
Kesalahan pertama adalah menyertakan Comparable lalu lupa mendefinisikan <=>, atau sebaliknya mendefinisikan <=> tanpa menyertakan modul Comparable. Kedua keadaan itu menghasilkan gejala yang sama, yaitu operator pembanding tidak dikenal. Program berikut menggunakan kelas Suhu yang sama sekali tidak memiliki <=>.
# Kelas tanpa <=> tidak mengenal operator perbandingan.
class Suhu
attr_reader :derajat
def initialize(derajat)
@derajat = derajat
end
end
begin
puts Suhu.new(21) < Suhu.new(25)
rescue NoMethodError => e
puts e.class
puts e.message
endNoMethodError
undefined method '<' for an instance of SuhuPesan error menyebut operator < karena pembanding pada Ruby adalah metode biasa yang dipanggil pada penerima, dan kelas Suhu versi ini tidak memilikinya. Perbaikannya adalah mendefinisikan <=> dan menyertakan Comparable berdampingan, sebagaimana contoh kelas Suhu pada bagian pertama, lalu seluruh operator lain mengikuti dengan sendirinya.
Kesalahan kedua adalah memanggil each tanpa blok pada kelas yang metode each-nya langsung memanggil yield. Kata kunci yield membutuhkan blok, sehingga pemanggilan tanpa blok berakhir dengan LocalJumpError.
class Buku
include Enumerable
def initialize(*bab)
@bab = bab
end
def each
@bab.each { |bab| yield bab }
end
end
buku = Buku.new("Pendahuluan", "Metode", "Penutup")
begin
buku.each
rescue LocalJumpError => e
puts e.class
puts e.message
endLocalJumpError
no block given (yield)Perbaikannya adalah menambahkan penjaga block_given? yang mengembalikan Enumerator ketika blok tidak diberikan.
class Buku
include Enumerable
def initialize(*bab)
@bab = bab
end
def each
return enum_for(:each) unless block_given?
@bab.each { |bab| yield bab }
end
end
buku = Buku.new("Pendahuluan", "Metode", "Penutup")
p buku.each.class
p buku.first(2)Enumerator
["Pendahuluan", "Metode"]Dengan penjaga itu, each tanpa blok tidak lagi gagal, melainkan menghasilkan Enumerator yang siap diiterasi kemudian, dan metode first dari Enumerable pun langsung dapat digunakan sebagaimana keluarannya. Pola penjaga yang sama digunakan kelas-kelas pustaka standar, sehingga each pada kelas buatan sendiri sebaiknya mengikuti kebiasaan tersebut sejak awal.
Ringkasan#
- Comparable menghidupkan
<,<=,==,>,>=, danbetween?dari satu definisi<=>yang menghasilkan bilangan negatif, nol, atau positif. <=>boleh menghasilkannilapabila kedua nilai tidak dapat dibandingkan, dan pembandingnya menjawab dengan ArgumentError.- Enumerable menghidupkan
map,select,sort,include?,min,max, dan metode-metode lainnya dari satu definisieach. - Kelas Buku pada contoh menyertakan keduanya, sehingga buku dapat dibandingkan menurut jumlah halaman dan diiterasi menurut halaman tiap bab.
Referensi#
- Comparable — Ruby
— Dokumentasi Comparable beserta daftar operator yang dibangun di atas
<=>. - Enumerable — Ruby
— Dokumentasi Enumerable dan kontrak metode
eachyang menjadi dasar seluruh metodenya.