Operator

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 mengembalikan false atau nil sebagai nilai yang valid, ||= akan menghitung ulang setiap kali dipanggil karena false dan nil adalah falsy. Dalam kasus ini, gunakan pola dengan defined? 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.

PrecedenceOperatorKeterangan
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
=, +=, `
notNegasi kata
Terendahand, orAND/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 .remainder yang 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 include Comparable untuk mendapat semua operator perbandingan secara gratis.
  • && dan || untuk ekspresi, and/or hanya untuk flow control — perbedaan precedence membuat and/or berbahaya dalam ekspresi assignment.
  • ||= untuk memoization dan nilai default — tapi waspadai jika method bisa mengembalikan nil atau false sebagai nilai valid.
  • Safe navigation &. mencegah NoMethodError — gunakan untuk chaining method pada nilai yang mungkin nil.
  • 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/or lebih rendah dari =x = a and b artinya (x = a) and b, bukan x = (a and b).

← Sebelumnya: Tipe Data   Berikutnya: Seleksi Kondisi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact