Ruby'de pattern matching, bir değerin yapısını case / in ifadesiyle kontrol edip eşleşen parçaları aynı anda yerel değişkenlere atamanızı sağlar. Dizi, hash, sınıf, aralık ve sabit değer desenlerini iç içe kullanabilir, ^ (pin) ile mevcut bir değişkenin değerini desene koyabilir, if ile ek koşul ekleyebilirsiniz. Tek satırlık => biçimi eşleşmezse hata fırlatır, in biçimi ise yalnızca true veya false döndürür.

Bu yazı Ruby 4.0 belgelerindeki pattern matching bölümünü temel alır ve her deseni çalıştırılmış örneklerle anlatır. Klasik case / when kullanımını henüz bilmiyorsanız önce Ruby if, unless ve case yazısına, genel bir yol haritası için Ruby öğrenme rehberi sayfasına bakabilirsiniz.

case / in ile ilk eşleşme

case / in ile ilk eşleşme

case / in ifadesi dalları sırayla dener ve eşleşen ilk dalı çalıştırır. when dallarından farklı olarak burada desen yalnızca değeri karşılaştırmaz, yapıyı da çözer. Belgelere göre aynı case içinde in ve when dallarını karıştıramazsınız. Hiçbir dal eşleşmezse ve else yoksa NoMatchingPatternError fırlatılır; bu yüzden ifade "kapsayıcı" kabul edilir.

ruby
def tarif_et(veri)
  case veri
  in Integer | Float => sayi if sayi.negative?
    "negatif sayı: #{sayi}"
  in Integer | Float
    "sayı"
  in String
    "metin"
  in []
    "boş dizi"
  in [x]
    "tek elemanlı dizi: #{x}"
  in {ad: String => ad}
    "ad alanı olan hash: #{ad}"
  else
    "tanınmadı"
  end
end

p tarif_et(-3)
p tarif_et(2.5)
p tarif_et("merhaba")
p tarif_et([])
p tarif_et([7])
p tarif_et({ad: "Elif", yas: 30})
p tarif_et(nil)
# çıktı: "negatif sayı: -3"
# çıktı: "sayı"
# çıktı: "metin"
# çıktı: "boş dizi"
# çıktı: "tek elemanlı dizi: 7"
# çıktı: "ad alanı olan hash: Elif"
# çıktı: "tanınmadı"

Bu kısa örnek birkaç desen türünü bir arada kullanıyor: Integer | Float alternatif desen, => sayi eşleşen değeri değişkene bağlayan "as" deseni, if ise guard koşuludur. Aşağıda her birini ayrı ayrı ele alıyoruz.

Değer desenleri: sınıflar, aralıklar ve sabitler

Bir desen yerine herhangi bir Ruby nesnesi yazdığınızda karşılaştırma === operatörüyle yapılır, tıpkı when dallarında olduğu gibi. Böylece sınıflar (Integer === 5), aralıklar ((1..10) === 5) ve düzenli ifadeler doğal olarak desen olarak çalışır. Kısa dallar için then ile tek satır yazabilirsiniz.

ruby
def not_harfi(puan)
  case puan
  in 85..100 then "AA"
  in 70...85 then "BB"
  in 50...70 then "CC"
  in 0...50  then "FF"
  end
end

p not_harfi(92)
p not_harfi(64)
begin
  not_harfi(150)
rescue NoMatchingPatternError => e
  puts "#{e.class}: #{e.message}"
end
# çıktı: "AA"
# çıktı: "CC"
# çıktı: NoMatchingPatternError: 150

Son çağrıda hiçbir dal eşleşmediği ve else olmadığı için istisna oluştu. Beklenmeyen girdinin sessizce geçmesini istemediğiniz yerlerde bu davranış işinize yarar; istemiyorsanız bir else dalı ekleyin. Aralıkların ayrıntısı için Ruby range yazısına bakabilirsiniz.

Dizi desenleri ve find pattern

Dizi deseni, dizinin tamamıyla eşleşir: [Integer, Integer] üç elemanlı bir diziyle eşleşmez. Kalan elemanları kabul etmek için *, bunları bir değişkende toplamak için *kalan yazarsınız. Köşeli parantezleri çoğu zaman atlayabilirsiniz.

ruby
komut = ["kopyala", "a.txt", "b.txt", "--zorla"]

case komut
in ["sil", dosya]
  puts "Silinecek: #{dosya}"
in ["kopyala", kaynak, hedef, *secenekler]
  puts "#{kaynak} -> #{hedef}, seçenekler: #{secenekler}"
end
# çıktı: a.txt -> b.txt, seçenekler: ["--zorla"]

Find pattern ise dizinin herhangi bir yerinde, art arda gelen elemanları arar. Desenin iki ucuna * koyarsınız; isterseniz bu uçlara isim vererek eşleşmenin öncesini ve sonrasını da alabilirsiniz. Ruby 3.2 NEWS dosyasına göre find pattern artık deneysel değildir.

ruby
olaylar = [
  {tur: "giris", kullanici: "ali"},
  {tur: "hata", kod: 500},
  {tur: "cikis", kullanici: "ali"}
]

case olaylar
in [*onceki, {tur: "hata", kod:}, *sonraki]
  puts "Hata kodu: #{kod}"
  puts "Öncesinde #{onceki.size}, sonrasında #{sonraki.size} olay var"
end
# çıktı: Hata kodu: 500
# çıktı: Öncesinde 1, sonrasında 1 olay var

Hash desenleri

Hash desenleri

Hash deseni dizi deseninin tersine kısmi eşleşir: desende yazmadığınız anahtarlar olsa da eşleşme başarılı olur. Yalnızca belirttiğiniz anahtarların bulunmasını istiyorsanız sona **nil eklersiniz; geri kalan anahtarları almak için **kalan yazarsınız. ad: gibi değersiz bir anahtar, aynı adlı bir yerel değişken oluşturur. Belgelerin vurguladığı önemli bir kısıt: hash desenlerinde yalnızca sembol anahtarlar desteklenir. Boş desen {} ise istisnadır ve yalnızca boş hash ile eşleşir.

ruby
yanit = {durum: 200, veri: {kullanici: {ad: "Can", rol: "admin"}}, sure: 0.12}

case yanit
in {durum: 200, veri: {kullanici: {rol: "admin", ad:}}}
  puts "Yönetici: #{ad}"
in {durum: 400..499 => kod}
  puts "İstemci hatası: #{kod}"
end

kisi = {ad: "Deniz", yas: 28, sehir: "İzmir"}
p((kisi in {ad: String}))
p((kisi in {ad: String, **nil}))
p(({"ad" => "Deniz"} in {ad: String}))

case kisi
in {ad:, **diger}
  p ad
  p diger
end
# çıktı: Yönetici: Can
# çıktı: true
# çıktı: false
# çıktı: false
# çıktı: "Deniz"
# çıktı: {yas: 28, sehir: "İzmir"}

Değişken bağlama ve pin operatörü (^)

Desende yalın bir isim yazdığınızda Ruby onu "buraya ne gelirse bu değişkene ata" olarak yorumlar. Bu yüzden mevcut bir değişkeni desene doğrudan yazmak, onun değeriyle karşılaştırma yapmaz; değişkenin üzerine yazar. Değeri desende kullanmak için başına ^ koyarsınız. Pin operatörü yerel değişkenlerin yanında örnek, sınıf ve genel değişkenlerle de çalışır; ^(ifade) biçimiyle herhangi bir ifadenin sonucunu da sabitleyebilirsiniz.

ruby
beklenen = 18
case 25
in beklenen
  puts "Eşleşti, beklenen artık #{beklenen}"
end

beklenen_kod = 404
case {durum: 404, yol: "/blog"}
in {durum: ^beklenen_kod, yol:}
  puts "Bulunamadı: #{yol}"
end

case [3, 3]
in [a, ^a]
  puts "İki eleman aynı: #{a}"
end

limit = 10
case 15
in ^(limit + 5)
  puts "Tam olarak limit + 5"
end
# çıktı: Eşleşti, beklenen artık 25
# çıktı: Bulunamadı: /blog
# çıktı: İki eleman aynı: 3
# çıktı: Tam olarak limit + 5

İlk örnek tipik bir tuzaktır: in beklenen her değerle eşleşir ve beklenen değişkenine 25 atanır. Üçüncü örnekte ise aynı desen içinde önce bağlanan a, ikinci konumda pin ile tekrar kullanılıyor; böylece "iki eleman eşit mi" kontrolü tek desende yapılıyor.

Guard koşulları: if ve unless

Desen eşleştikten sonra ek bir koşul kontrol etmek için dalın sonuna if veya unless yazarsınız. Koşul, desenin bağladığı değişkenleri kullanabilir. Koşul sağlanmazsa Ruby bir sonraki dala geçer.

ruby
def kontrol(cift)
  case cift
  in [a, b] if b == a * 2 then "ikincisi birincinin iki katı"
  in [a, b] unless a == b then "farklı sayılar"
  in [_, _] then "eşit sayılar"
  end
end

p kontrol([3, 6])
p kontrol([3, 5])
p kontrol([4, 4])
# çıktı: "ikincisi birincinin iki katı"
# çıktı: "farklı sayılar"
# çıktı: "eşit sayılar"

Belgelere göre guard koşulu yalnızca case / in dallarında geçerlidir. Tek satırlık => ve in biçimlerinin sonuna yazılan if, guard olarak değil, satırın tamamına uygulanan sıradan bir if değiştiricisi olarak yorumlanır.

Tek satırlık biçimler: => ve in

Verinin yapısını önceden biliyorsanız ve yalnızca parçalarını çıkarmak istiyorsanız ifade => desen biçimi kısa ve nettir. Eşleşme başarısız olursa istisna fırlatılır; hash deseninde eksik bir anahtar yüzünden başarısız olduğunda bu istisna NoMatchingPatternKeyError olur ve key metodu eksik anahtarı verir. ifade in desen ise yalnızca eşleşip eşleşmediğini söyler; belgelere göre bu, true / false döndüren bir case ifadesiyle aynıdır.

ruby
ayar = {db: {kullanici: "admin", port: 5432}}
ayar => {db: {kullanici:, port:}}
puts "#{kullanici}@#{port}"

kullanicilar = [{ad: "Ayşe", yas: 17}, {ad: "Burak", yas: 23}]
p kullanicilar.select { it in {yas: 18..} }.map { it[:ad] }

begin
  ayar => {cache: {ttl:}}
rescue NoMatchingPatternKeyError => e
  puts e.class
  p e.key
end
# çıktı: admin@5432
# çıktı: ["Burak"]
# çıktı: NoMatchingPatternKeyError
# çıktı: :cache

Örnekteki it, Ruby 3.4 ile gelen örtük blok parametresidir; ayrıntısı Ruby blok parametreleri: it ve numaralı yazısında. Ruby 3.1 NEWS dosyasına göre tek satırlık pattern matching artık deneysel değildir ve desen etrafındaki parantezler atlanabilir.

Kendi sınıflarınızla eşleşme: deconstruct ve deconstruct_keys

Dizi ve find desenleri, dizi olmayan bir nesneyle karşılaştığında onun deconstruct metodunu; hash desenleri ise deconstruct_keys metodunu çağırır. Bu iki metodu tanımlayarak kendi sınıfınızı desenlerle kullanılabilir hale getirirsiniz. Desenin başına sınıf adını yazarak Nokta(x:, y:) veya Nokta[x, y] biçiminde tür kontrolünü de aynı adımda yapabilirsiniz; sınıf kontrolü === ile yapılır.

ruby
class Nokta
  attr_reader :x, :y

  def initialize(x, y)
    @x = x
    @y = y
  end

  def deconstruct = [x, y]
  def deconstruct_keys(keys) = {x: x, y: y}
end

def konum(nokta)
  case nokta
  in Nokta(x: 0, y: 0) then "orijin"
  in Nokta(x: 0) then "y ekseni üzerinde"
  in Nokta[x, 0] then "x ekseni üzerinde, x = #{x}"
  in Nokta(x:, y:) if x == y then "köşegen üzerinde"
  else "başka bir yerde"
  end
end

[[0, 0], [0, 5], [3, 0], [2, 2], [1, 4]].each do |x, y|
  puts konum(Nokta.new(x, y))
end
# çıktı: orijin
# çıktı: y ekseni üzerinde
# çıktı: x ekseni üzerinde, x = 3
# çıktı: köşegen üzerinde
# çıktı: başka bir yerde

deconstruct_keys metoduna verilen keys argümanı, desenin hangi anahtarları istediğini söyler. Tam hash'i hesaplamak pahalıysa yalnızca istenenleri döndürebilirsiniz. Desende **kalan kullanıldığında ise tüm anahtarlar gerekebileceği için nil gönderilir:

ruby
class Kayit
  def deconstruct_keys(keys)
    puts "istenen anahtarlar: #{keys.inspect}"
    {id: 1, ad: "test", gizli: "x"}
  end
end

case Kayit.new
in {id:, ad:} then puts "id=#{id}"
end

case Kayit.new
in {id:, **geri} then p geri
end
# çıktı: istenen anahtarlar: [:id, :ad]
# çıktı: id=1
# çıktı: istenen anahtarlar: nil
# çıktı: {ad: "test", gizli: "x"}

Çekirdekte bu metotları hazır sunan sınıflar da var. Belgeler MatchData#deconstruct ve MatchData#deconstruct_keys ile Time, Date ve DateTime sınıflarının deconstruct_keys metodunu sayar. Böylece bir düzenli ifade eşleşmesini doğrudan hash deseniyle açabilirsiniz; adlandırılmış gruplar sembol anahtar olarak gelir. Düzenli ifadelerin ayrıntısı Ruby Regexp yazısında.

ruby
case /(?<yil>\d{4})-(?<ay>\d{2})/.match("Rapor 2026-11")
in {yil:, ay:}
  puts "Yıl #{yil}, ay #{ay}"
end
# çıktı: Yıl 2026, ay 11

Değer nesnesi tanımlamak için kullanılan Struct ve Data sınıflarını Ruby Struct ve Data sınıfı yazısında ele alıyoruz.

Desen türleri özeti

DesenÖrnekNe yapar
Değerin 1..10, in StringDeğeri === ile karşılaştırır
Diziin [a, b, *kalan]Dizinin tamamıyla eşleşir
Findin [*, {tur: "hata"}, *]Dizinin içinde ardışık parçayı arar
Hashin {ad:, yas: Integer}Kısmi eşleşir, yalnızca sembol anahtar
Alternatifin Integer | FloatSeçeneklerden biri tutarsa eşleşir
As (bağlama)in Integer => nEşleşen değeri değişkene atar
Pinin ^beklenenMevcut değişkenin değerini kullanır
Guardin [a, b] if a < bEşleşmeden sonra ek koşul
Sınıf ilein Nokta(x:, y:)Tür kontrolü ve çözümleme birlikte

Sürüm notları

NEWS dosyalarına göre case / in Ruby 3.0 ile deneysel olmaktan çıktı; tek satırlık biçim 3.1'de, find pattern 3.2'de kalıcı hale geldi. MatchData#deconstruct ve MatchData#deconstruct_keys da 3.2 ile eklendi. Ruby 4.0 NEWS dosyası pattern matching söz dizimi için yeni bir değişiklik listelemiyor; yukarıdaki örneklerin tamamı Ruby 4.0.9 ile çalıştırıldı.

Sık yapılan hatalar

Sık yapılan hatalar: Pin unutmak, Diziyi kısmi sanmak, Metin anahtarlı hash, else unutmak
  • Pin unutmak: Mevcut bir değişkeni desene ^ olmadan yazmak karşılaştırma yapmaz, değişkenin üzerine yazar ve her değerle eşleşir.
  • Diziyi kısmi sanmak: [a, b] deseni üç elemanlı diziyle eşleşmez. Fazlasını kabul etmek için * ekleyin.
  • Metin anahtarlı hash: {"ad" => "x"} verisi {ad:} deseniyle eşleşmez; hash desenleri yalnızca sembol anahtarlarla çalışır.
  • else unutmak: Hiçbir dal tutmazsa NoMatchingPatternError oluşur. Bu bilinçli bir tercih değilse else ekleyin.
  • Alternatifte bağlama: in {a:} | Array gibi bir desen SyntaxError verir.
  • when ile karıştırmak: Aynı case içinde hem when hem in kullanılamaz.

Sık Sorulan Sorular

case / when ile case / in arasındaki fark nedir?

when değeri === ile karşılaştırır ve yalnızca evet ya da hayır der. in ise aynı karşılaştırmayı yaparken yapıyı da çözer, iç içe dizi ve hash'lere bakar ve eşleşen parçaları değişkenlere bağlar. Ayrıca case / in hiçbir dal tutmadığında istisna fırlatır, case / when ise nil döndürür.

=&gt; ile in arasında hangisini seçmeliyim?

Veri yapısının belli olduğunu varsayıyor ve aksi durumda programın hata vermesini istiyorsanız => kullanın. Sadece kontrol yapmak, örneğin select içinde süzmek istiyorsanız in kullanın; o hiçbir zaman eşleşme hatası fırlatmaz.

Hash deseninde metin anahtar kullanabilir miyim?

Hayır, belgelere göre hash desenleri yalnızca sembol anahtarları destekler. Metin anahtarlı bir hash'i önce transform_keys(&:to_sym) gibi bir dönüşümle sembol anahtarlı hale getirebilirsiniz.

Kendi sınıfım için hangi metodu yazmalıyım?

Dizi ve find desenleri için deconstruct (dizi döndürür), hash desenleri için deconstruct_keys(keys) (sembol anahtarlı hash döndürür). İkisini birden tanımlarsanız sınıfınız her iki desen türüyle de kullanılabilir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Ruby öğrenme rehberi · Ruby if, unless ve case · Ruby hash metotları · Ruby array metotları · Ruby Struct ve Data · Ruby Regexp · Ruby hata yönetimi