Operator #
Operator adalah jantung dari ekspresi — tanpanya, kode hanya berisi nilai yang berdiri sendiri tanpa hubungan satu sama lain. Ruby punya semua operator standar yang kamu temukan di bahasa lain, ditambah beberapa operator unik yang tidak ada di tempat lain: spaceship operator (<=>), safe navigation operator (&.), dan conditional assignment (||=, &&=). Hal lain yang membuat operator Ruby menarik adalah hampir semua operator sebenarnya adalah method — artinya kamu bisa mendefinisikan ulang atau menambahkan operator ke kelas buatan sendiri. Artikel ini membahas semua operator Ruby dari yang paling dasar hingga yang paling idiomatik, beserta jebakan umum yang sering dialami developer baru.
Operator Aritmatika #
Operator aritmatika adalah yang paling familiar — tapi Ruby punya beberapa perilaku yang perlu diperhatikan, terutama pada pembagian integer dan modulo dengan bilangan negatif.
a = 17
b = 5
puts a + b # => 22 (penjumlahan)
puts a - b # => 12 (pengurangan)
puts a * b # => 85 (perkalian)
puts a / b # => 3 (pembagian integer — desimal dibuang!)
puts a % b # => 2 (modulo — sisa bagi)
puts a ** b # => 1419857 (pangkat: 17⁵)
puts -a # => -17 (negasi unary)
Pembagian Integer vs Float #
Ini adalah sumber kebingungan paling umum untuk pemula Ruby:
# ANTI-PATTERN: lupa bahwa pembagian dua Integer menghasilkan Integer
def rata_rata(total, jumlah)
total / jumlah # jika keduanya Integer, hasilnya Integer!
end
puts rata_rata(10, 3) # => 3 (bukan 3.333!)
# BENAR — tiga cara untuk mendapatkan hasil Float:
puts 10.fdiv(3) # => 3.3333... — paling idiomatik Ruby
puts 10.to_f / 3 # => 3.3333... — konversi eksplisit
puts 10 / 3.0 # => 3.3333... — paksa satu operand Float
puts Rational(10, 3).to_f # => 3.3333... — via Rational
Modulo vs Remainder #
Ruby menggunakan % sebagai modulo (bukan remainder), yang punya perilaku berbeda untuk bilangan negatif:
# Modulo di Ruby — hasilnya selalu punya tanda yang sama dengan PEMBAGI
puts 7 % 3 # => 1
puts -7 % 3 # => 2 (bukan -1!)
puts 7 % -3 # => -2 (bukan 1!)
puts -7 % -3 # => -1
# Ruby juga punya .remainder yang mengikuti tanda YANG DIBAGI
puts 7.remainder(3) # => 1
puts (-7).remainder(3) # => -1 (ikut tanda -7)
puts 7.remainder(-3) # => 1 (ikut tanda 7)
# Kegunaan praktis modulo — rotasi array / jam
jam_sekarang = 23
setelah = (jam_sekarang + 3) % 24 # => 2 (bukan 26)
hari = ["Sen", "Sel", "Rab", "Kam", "Jum", "Sab", "Min"]
puts hari[6 % 7] # => "Min"
puts hari[7 % 7] # => "Sen" (kembali ke awal)
Operator Perbandingan #
Operator perbandingan mengembalikan true atau false, kecuali spaceship operator (<=>) yang mengembalikan -1, 0, atau 1.
a = 10
b = 20
puts a == b # => false (sama dengan — nilai)
puts a != b # => true (tidak sama dengan)
puts a > b # => false (lebih besar)
puts a < b # => true (lebih kecil)
puts a >= b # => false (lebih besar atau sama)
puts a <= b # => true (lebih kecil atau sama)
== vs equal? vs eql? #
Ruby punya tiga operator/method untuk membandingkan kesamaan, dan ketiganya punya arti yang berbeda:
# == : kesamaan NILAI (bisa di-override di kelas)
puts 1 == 1.0 # => true (nilai sama meski tipe berbeda)
puts "a" == "a" # => true (isi string sama)
# equal? : kesamaan IDENTITAS OBJEK (object_id sama)
puts 1.equal?(1) # => true (Integer kecil di-cache Ruby)
puts "a".equal?("a") # => false (dua objek String berbeda di memori)
puts :sym.equal?(:sym) # => true (Symbol adalah singleton)
# eql? : kesamaan NILAI dan TIPE (digunakan Hash untuk membandingkan key)
puts 1.eql?(1) # => true
puts 1.eql?(1.0) # => false (tipe berbeda: Integer vs Float)
puts "a".eql?("a") # => true
Panduan memilih operator kesamaan:
== → hampir selalu ini yang kamu mau (kesamaan nilai)
equal? → cek apakah dua variabel menunjuk ke objek yang PERSIS sama
eql? → jarang dipakai langsung, tapi penting untuk Hash key
Spaceship Operator <=> #
Spaceship operator adalah operator perbandingan tiga-arah yang mengembalikan -1, 0, atau 1. Ia adalah fondasi dari semua sorting di Ruby.
puts 1 <=> 2 # => -1 (kiri lebih kecil)
puts 2 <=> 2 # => 0 (sama)
puts 3 <=> 2 # => 1 (kiri lebih besar)
puts "a" <=> "b" # => -1
puts "b" <=> "a" # => 1
# Cara kerja Array#sort menggunakan <=>
angka = [5, 2, 8, 1, 9, 3]
angka.sort # => [1, 2, 3, 5, 8, 9] (ascending, default)
# sort_by — lebih idiomatik untuk sorting berdasarkan atribut objek
orang = [
{ nama: "Citra", umur: 28 },
{ nama: "Andi", umur: 35 },
{ nama: "Bagas", umur: 22 }
]
orang.sort_by { |p| p[:umur] }
# => [{nama: "Bagas", umur: 22}, {nama: "Citra", umur: 28}, {nama: "Andi", umur: 35}]
orang.sort_by { |p| [-p[:umur], p[:nama]] } # umur desc, nama asc jika sama
# Implementasi Comparable — include module untuk dapat semua operator perbandingan
class Produk
include Comparable
attr_reader :nama, :harga
def initialize(nama, harga)
@nama = nama
@harga = harga
end
def <=>(other)
@harga <=> other.harga # definisikan dasar perbandingan
end
end
laptop = Produk.new("Laptop", 15_000_000)
mouse = Produk.new("Mouse", 350_000)
monitor = Produk.new("Monitor", 3_500_000)
produk = [laptop, mouse, monitor]
produk.sort.map(&:nama) # => ["Mouse", "Monitor", "Laptop"] — otomatis bisa sort!
puts laptop > mouse # => true
puts mouse.between?(mouse, laptop) # => true — dapat gratis dari Comparable
Operator Logika #
Ruby punya dua set operator logika: simbol (&&, ||, !) dan kata (and, or, not). Keduanya berfungsi secara logis sama, tapi punya precedence yang sangat berbeda.
a = true
b = false
puts a && b # => false (AND)
puts a || b # => true (OR)
puts !a # => false (NOT)
puts !b # => true
Short-Circuit Evaluation #
Operator && dan || menggunakan short-circuit evaluation — operand kanan hanya dievaluasi jika diperlukan. Ini bukan sekadar optimisasi performa; ia sering digunakan secara sengaja dalam Ruby idiomatik.
# && berhenti di operand PERTAMA yang falsy
puts false && expensive_operation() # expensive_operation tidak dipanggil!
puts nil && raise("Tidak sampai") # raise tidak dieksekusi
# || berhenti di operand PERTAMA yang truthy
puts true || expensive_operation() # expensive_operation tidak dipanggil!
puts "ada" || raise("Tidak sampai") # raise tidak dieksekusi
# Pola praktis: guard clause dengan &&
def proses(user)
user && user.aktif? && user.punya_izin?(:proses)
end
# Pola praktis: nilai default dengan ||
nama = input_dari_user || "Tamu"
config = ENV["TIMEOUT"] || "30"
&& vs and, || vs or #
Ini adalah salah satu sumber bug paling halus di Ruby — perbedaan precedence antara simbol dan kata:
# && memiliki precedence LEBIH TINGGI dari =
# || memiliki precedence LEBIH TINGGI dari =
nilai = true && false # => nilai = (true && false) = false
nilai = true || false # => nilai = (true || false) = true
# and memiliki precedence LEBIH RENDAH dari =
# or memiliki precedence LEBIH RENDAH dari =
nilai = true and false # => (nilai = true) and false → nilai = true !
nilai = true or false # => (nilai = true) or false → nilai = true !
# ANTI-PATTERN: menggunakan and/or untuk assignment kondisional
x = some_value and do_something # mengejutkan karena precedence
# BENAR: gunakan && dan || untuk ekspresi kondisional
x = some_value && do_something
# and dan or hanya layak digunakan untuk flow control sederhana — bukan ekspresi
File.exist?(path) or raise "File tidak ditemukan" # ok tapi lebih baik pakai raise unless
raise "File tidak ditemukan" unless File.exist?(path) # lebih idiomatik
Operator Penugasan #
Operator penugasan di Ruby jauh lebih kaya dari sekadar =. Ada compound assignment untuk setiap operator aritmatika dan bitwise, ditambah dua operator conditional assignment yang sangat idiomatik.
Penugasan Dasar dan Compound #
a = 10 # penugasan dasar
a += 5 # a = a + 5 => 15
a -= 3 # a = a - 3 => 12
a *= 2 # a = a * 2 => 24
a /= 4 # a = a / 4 => 6
a %= 4 # a = a % 4 => 2
a **= 3 # a = a ** 3 => 8
# Compound assignment untuk bitwise
b = 0b1010
b &= 0b1100 # => 0b1000 (AND)
b |= 0b0011 # => 0b1011 (OR)
b ^= 0b0101 # => 0b1110 (XOR)
b <<= 2 # => shift kiri 2 bit
b >>= 1 # => shift kanan 1 bit
Conditional Assignment — ||= dan &&= #
Dua operator ini adalah salah satu fitur paling idiomatik Ruby yang membuat kode sangat ringkas:
# ||= : assign hanya jika variabel saat ini falsy (nil atau false)
@cache ||= {} # inisialisasi hanya jika belum ada
nama ||= "Tamu" # gunakan "Tamu" jika nama nil atau false
config ||= muat_config() # panggil muat_config() hanya jika config belum ada
# Cara baca: "assign jika belum ada nilainya"
# Ekuivalen dengan:
@cache = @cache || {}
nama = nama || "Tamu"
# &&= : assign hanya jika variabel saat ini truthy
# "assign hanya jika sudah ada nilainya"
user.nama &&= user.nama.strip # strip hanya jika user.nama tidak nil
data &&= proses(data) # proses hanya jika data ada
# Penggunaan ||= yang paling umum: memoization
class LaporanBulanan
def total_penjualan
@total_penjualan ||= hitung_dari_database # hitung sekali, cache selamanya
end
private
def hitung_dari_database
puts "Menghitung dari DB..." # hanya muncul sekali
# query yang mahal...
150_000_000
end
end
laporan = LaporanBulanan.new
puts laporan.total_penjualan # => "Menghitung dari DB..." => 150000000
puts laporan.total_penjualan # => 150000000 (langsung dari cache, tanpa query)
Memoization dengan||=punya satu kelemahan: jika method-mu bisa mengembalikanfalseataunilsebagai nilai yang valid,||=akan menghitung ulang setiap kali dipanggil karenafalsedanniladalah falsy. Dalam kasus ini, gunakan pola dengandefined?atau variabel sentinel:@hasil = hitung() unless defined?(@hasil).
Operator Ternary #
Operator ternary (?:) adalah bentuk ringkas dari if-else untuk ekspresi satu baris yang mengembalikan nilai.
# Sintaks: kondisi ? nilai_jika_true : nilai_jika_false
umur = 20
status = umur >= 18 ? "dewasa" : "anak-anak"
puts status # => "dewasa"
# Penggunaan umum: assignment kondisional ringkas
diskon = member? ? 0.15 : 0.0
label = saldo > 0 ? "Kredit" : "Debit"
warna = error? ? :merah : :hijau
# ANTI-PATTERN: ternary bertumpuk — sangat sulit dibaca
hasil = a > 0 ? (b > 0 ? "keduanya positif" : "hanya a positif") : "a negatif atau nol"
# BENAR: gunakan if-elsif untuk kondisi bertumpuk
hasil = if a > 0 && b > 0
"keduanya positif"
elsif a > 0
"hanya a positif"
else
"a negatif atau nol"
end
# ANTI-PATTERN: ternary untuk efek samping (bukan mengembalikan nilai)
kondisi ? puts("ya") : puts("tidak") # membingungkan
# BENAR: if-else untuk efek samping
if kondisi
puts "ya"
else
puts "tidak"
end
Operator Range #
Operator range (.. dan ...) membuat objek Range yang merepresentasikan urutan nilai berurutan.
inklusif = 1..10 # 1, 2, 3, ..., 10 (termasuk 10)
eksklusif = 1...10 # 1, 2, 3, ..., 9 (tidak termasuk 10)
# Iterasi
(1..5).each { |n| print "#{n} " } # => 1 2 3 4 5
# Pengecekan keanggotaan
puts (1..10).include?(5) # => true
puts (1..10).include?(10) # => true
puts (1...10).include?(10) # => false
# Penggunaan dalam case/when — sangat idiomatik Ruby
def klasifikasi_bmi(bmi)
case bmi
when ...18.5 then "Kekurangan berat badan"
when 18.5..24.9 then "Normal"
when 25.0..29.9 then "Kelebihan berat badan"
when 30.0.. then "Obesitas"
end
end
puts klasifikasi_bmi(22.5) # => "Normal"
puts klasifikasi_bmi(31.0) # => "Obesitas"
# Endless range (Ruby 2.6+) — tanpa batas atas
(18..) # berarti "18 ke atas, tidak ada batas"
(..17) # berarti "sampai dengan 17" (beginless range, Ruby 2.7+)
umur = 25
puts (18..).include?(umur) # => true
# Step pada range
(0..20).step(5).to_a # => [0, 5, 10, 15, 20]
(0.0..1.0).step(0.25).to_a # => [0.0, 0.25, 0.5, 0.75, 1.0]
Operator Bitwise #
Operator bitwise bekerja langsung pada representasi biner dari Integer. Paling sering digunakan untuk manipulasi flag, masking, dan operasi tingkat rendah.
a = 0b1010_1010 # 170 dalam desimal
b = 0b1100_1100 # 204 dalam desimal
puts (a & b).to_s(2) # => "10001000" — AND bitwise
puts (a | b).to_s(2) # => "11101110" — OR bitwise
puts (a ^ b).to_s(2) # => "1100110" — XOR bitwise (berbeda)
puts (~a & 0xFF).to_s(2) # => "1010101" — NOT bitwise (mask ke 8 bit)
puts (a << 2).to_s(2) # => "1010101000" — shift kiri 2 bit (kalikan 4)
puts (a >> 2).to_s(2) # => "101010" — shift kanan 2 bit (bagi 4)
# Penggunaan praktis: bit flags untuk permission
BACA = 0b001 # 1
TULIS = 0b010 # 2
HAPUS = 0b100 # 4
# Set permission
izin_editor = BACA | TULIS # => 0b011 = 3
izin_admin = BACA | TULIS | HAPUS # => 0b111 = 7
# Cek permission
def bisa_baca?(izin)
izin & BACA != 0
end
def bisa_hapus?(izin)
izin & HAPUS != 0
end
puts bisa_baca?(izin_editor) # => true
puts bisa_hapus?(izin_editor) # => false
puts bisa_hapus?(izin_admin) # => true
# Cabut permission
izin_editor_tanpa_tulis = izin_editor & ~TULIS
puts bisa_tulis?(izin_editor_tanpa_tulis) # => false
Operator Unik Ruby #
Selain operator standar, Ruby punya beberapa operator yang tidak ada (atau jarang ada) di bahasa lain.
Safe Navigation Operator (&.) #
Operator &. (disebut lonely operator atau safe navigation) memanggil method hanya jika objek tidak nil, mencegah NoMethodError.
# ANTI-PATTERN: pengecekan nil yang verbose
if user && user.profil && user.profil.alamat
puts user.profil.alamat.kota
end
# BENAR: safe navigation operator
puts user&.profil&.alamat&.kota
# Sangat berguna untuk chaining method pada nilai yang mungkin nil
nama_kota = pesanan&.pengiriman&.alamat&.kota || "Tidak diketahui"
email_atas = user&.email&.upcase
Defined? Operator #
defined? adalah operator yang mengecek apakah sebuah ekspresi terdefinisi, mengembalikan String deskriptif atau nil.
x = 10
puts defined?(x) # => "local-variable"
puts defined?(y) # => nil (y belum didefinisikan)
puts defined?(String) # => "constant"
puts defined?(puts) # => "method"
puts defined?(@ivar) # => nil (jika belum di-set)
puts defined?(1 + 1) # => "expression"
# Penggunaan: inisialisasi yang benar-benar aman (bukan ||=)
@hasil = hitung() unless defined?(@hasil)
# Aman bahkan jika hitung() mengembalikan nil atau false
Pattern Matching Operator (=>) #
Sejak Ruby 3.0, operator => digunakan untuk pattern matching — cara mendestrukturisasi data secara ekspresif.
# Rightward assignment (Ruby 3.0+)
hitung_total(pesanan) => total
# Deconstruct hash
data = { nama: "Rina", umur: 28, kota: "Bandung" }
data => { nama:, umur: } # ekstrak nama dan umur ke variabel lokal
puts nama # => "Rina"
puts umur # => 28
# Pattern matching dengan case/in (Ruby 3.x)
response = { status: 200, body: { user: { nama: "Budi" } } }
case response
in { status: 200, body: { user: { nama: String => nama } } }
puts "Login berhasil: #{nama}"
in { status: 401 }
puts "Tidak terotorisasi"
in { status: 500 }
puts "Server error"
end
Precedence (Urutan Prioritas Operator) #
Ketika sebuah ekspresi mengandung beberapa operator, Ruby mengevaluasinya berdasarkan urutan precedence. Operator dengan precedence lebih tinggi dievaluasi lebih dulu.
| Precedence | Operator | Keterangan |
|---|---|---|
| Tertinggi | !, ~, unary + | Negasi dan komplemen |
** | Pangkat | |
unary - | Negasi numerik | |
*, /, % | Perkalian, pembagian, modulo | |
+, - | Penjumlahan, pengurangan | |
<<, >> | Shift bitwise | |
& | AND bitwise | |
^, | | XOR dan OR bitwise | |
<=, <, >, >= | Perbandingan | |
<=>, ==, ===, !=, =~, !~ | Kesamaan dan match | |
&& | AND logika | |
|| | OR logika | |
.., ... | Range | |
? : | Ternary | |
=, +=, ` | ||
not | Negasi kata | |
| Terendah | and, or | AND/OR kata |
# Contoh precedence dalam praktik
puts 2 + 3 * 4 # => 14 (bukan 20 — * lebih tinggi dari +)
puts (2 + 3) * 4 # => 20 (kurung memaksa urutan)
puts !false || true # => true (!false dulu, baru ||)
puts !(false || true) # => false (kurung dulu)
# Jebakan precedence yang umum
x = 1 + 2 == 3 # => x = (1 + 2 == 3) = true (bukan x = 1 + (2 == 3))
y = true || false && false # => y = true || (false && false) = true
Operator Kustom di Kelas Sendiri #
Karena operator di Ruby pada dasarnya adalah method, kamu bisa mendefinisikan perilaku operator untuk kelas buatan sendiri. Ini adalah salah satu fitur paling powerful Ruby.
class Vektor
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def +(other)
Vektor.new(@x + other.x, @y + other.y)
end
def -(other)
Vektor.new(@x - other.x, @y - other.y)
end
def *(skalar)
Vektor.new(@x * skalar, @y * skalar)
end
def ==(other)
@x == other.x && @y == other.y
end
def <=>(other)
magnitude <=> other.magnitude
end
def [](index)
index == 0 ? @x : @y # akses seperti array: v[0], v[1]
end
def magnitude
Math.sqrt(@x**2 + @y**2)
end
def to_s
"(#{@x}, #{@y})"
end
end
v1 = Vektor.new(3, 4)
v2 = Vektor.new(1, 2)
puts v1 + v2 # => (4, 6)
puts v1 - v2 # => (2, 2)
puts v1 * 3 # => (9, 12)
puts v1 == Vektor.new(3, 4) # => true
puts v1[0] # => 3
puts v1[1] # => 4
puts v1.magnitude # => 5.0
vektor = [v2, v1, Vektor.new(0, 1)]
vektor.sort.map(&:to_s) # => ["(0, 1)", "(1, 2)", "(3, 4)"]
# Operator yang BISA didefinisikan di kelas sendiri:
# + - * / % ** (aritmatika)
# == <=> < > <= >= (perbandingan)
# & | ^ ~ << >> (bitwise)
# [] []= (akses elemen)
# ! (negasi)
# Operator yang TIDAK BISA didefinisikan ulang:
# && || and or not (logika — bukan method)
# = += ||= dll (assignment — bukan method)
# .. ... (range — bukan method)
# ? : (ternary — bukan method)
Ringkasan #
- Pembagian dua Integer menghasilkan Integer — gunakan
fdiv,to_f, atau satu operand Float jika butuh hasil desimal.- Modulo
%ikut tanda pembagi — berbeda dari.remainderyang ikut tanda yang dibagi. Penting untuk bilangan negatif.==untuk nilai,equal?untuk identitas,eql?untuk nilai+tipe — hampir selalu gunakan==dalam kode sehari-hari.- Spaceship
<=>adalah fondasi sorting — implement di kelas sendiri dan includeComparableuntuk mendapat semua operator perbandingan secara gratis.&&dan||untuk ekspresi,and/orhanya untuk flow control — perbedaan precedence membuatand/orberbahaya dalam ekspresi assignment.||=untuk memoization dan nilai default — tapi waspadai jika method bisa mengembalikannilataufalsesebagai nilai valid.- Safe navigation
&.mencegah NoMethodError — gunakan untuk chaining method pada nilai yang mungkinnil.- Ternary untuk ekspresi satu baris — jangan tumpuk dan jangan gunakan untuk efek samping tanpa return value.
- Operator di Ruby adalah method — kamu bisa mendefinisikan
+,-,[],==,<=>, dan lainnya di kelas sendiri untuk API yang ekspresif dan natural.- Precedence
and/orlebih rendah dari=—x = a and bartinya(x = a) and b, bukanx = (a and b).