Ruby'de keyword argümanları, metoda değeri adıyla birlikte vermenizi sağlar: baglan(host: "db", port: 5432). Tanımda port: 5432 yazarsanız parametre varsayılan değerli olur, port: yazıp değeri boş bırakırsanız zorunlu olur. **opts tanımda bilinmeyen tüm anahtarları bir Hash'te toplar, ... ise bir metodun aldığı bütün argümanları (konumsal, keyword ve blok) başka bir metoda olduğu gibi aktarır.

Keyword argümanları çağrı sırasından bağımsızdır ve hangi değerin neye karşılık geldiğini çağrı satırında açıkça gösterir. Bu yazıda tanım ve çağrı kurallarını, Ruby 3.0 ile gelen Hash/keyword ayrımını, ** ile Hash açmayı ve anonim *, **, & yönlendirmeyi örneklerle anlatıyoruz. Örnekler Ruby 4.0 ile çalıştırıldı. Konumsal parametrelerin temeli için önce metot tanımlama yazısını okumanızı öneririz.

Varsayılan değerli keyword argümanları

Varsayılan değerli keyword argümanları

Belgeler keyword argümanlarını, varsayılan değerli konumsal argümanlara benzetir. Fark şudur: çağrıda adlarını yazarsınız ve istediğiniz sırada verebilirsiniz. Vermediğiniz anahtarlar tanımdaki varsayılan değeri alır.

ruby
def baglan(host: "localhost", port: 5432)
  "#{host}:#{port}"
end

p baglan                                 # çıktı: "localhost:5432"
p baglan(port: 6543)                     # çıktı: "localhost:6543"
p baglan(port: 6543, host: "db.local")   # çıktı: "db.local:6543"

Zorunlu keyword argümanları

Bir keyword argümanını zorunlu yapmak için varsayılan değer yazmazsınız: def kullanici(ad:, yas:). Çağıran bu anahtarlardan birini unutursa ya da metodun tanımadığı bir anahtar gönderirse ArgumentError oluşur. Hata mesajı eksik ya da bilinmeyen anahtarı sembol olarak gösterir, bu da hatayı bulmayı kolaylaştırır.

ruby
def kullanici(ad:, yas:)
  "#{ad} (#{yas})"
end
p kullanici(yas: 30, ad: "Deniz") # çıktı: "Deniz (30)"

[
  -> { kullanici(ad: "Deniz") },
  -> { kullanici(ad: "Deniz", yas: 30, sehir: "İzmir") },
  -> { kullanici }
].each do |cagri|
  cagri.call
rescue ArgumentError => e
  puts "#{e.class}: #{e.message}"
end
# çıktı: ArgumentError: missing keyword: :yas
# çıktı: ArgumentError: unknown keyword: :sehir
# çıktı: ArgumentError: missing keywords: :ad, :yas

Konumsal ve keyword argümanlarını birlikte kullanıyorsanız, belgelere göre tüm konumsal argümanlar keyword argümanlarından önce gelmelidir. Çağrı tarafında da sıra konumsal argümanlar, keyword argümanları ve en sonda blok şeklindedir.

**opts ile serbest anahtarlar toplamak

Tanımda ** ile başlayan bir parametre, metodun açıkça listelemediği bütün keyword argümanlarını bir Hash olarak toplar. Böylece bilinmeyen anahtarlar hata vermez. Belgelerde **nil adlı özel bir yazım da var: metodun keyword kabul etmediğini açıkça belirtir ve keyword ile çağrılırsa hata verir. Belgeler bunun, ileride metoda geriye dönük uyumluluğu bozmadan keyword eklenebilmesi için desteklendiğini açıklar.

ruby
def kaydet(ad, sessiz: false, **ekler)
  [ad, sessiz, ekler]
end
p kaydet("rapor", sessiz: true, format: "pdf", sayfa: 3)
# çıktı: ["rapor", true, {format: "pdf", sayfa: 3}]
p kaydet("rapor") # çıktı: ["rapor", false, {}]

def anahtarsiz(**nil); end
begin
  anahtarsiz(x: 1)
rescue ArgumentError => e
  puts "#{e.class}: #{e.message}" # çıktı: ArgumentError: no keywords accepted
end

Çıktıdaki {format: "pdf", sayfa: 3} biçimine dikkat edin: Ruby 4.0'da inspect, sembol anahtarlı Hash'leri bu kısa biçimde, metin anahtarlıları ise {"k" => 1} biçiminde yazdırır. Hash'in kendisiyle ilgili metotlar için Ruby Hash metotları yazısına bakabilirsiniz.

Hash mi keyword mü? Ruby 3.0 ayrımı

Hash mi keyword mü? Ruby 3.0 ayrımı

Ruby 3.0 NEWS dosyasına göre bu sürümle keyword argümanları konumsal argümanlardan ayrıldı; Ruby 2.7'de uyarı veren kodlar 3.0'da ArgumentError verir ya da farklı davranır. Bugünkü kurallar şöyle özetlenebilir: Metot hiç keyword tanımlamıyorsa ve **nil da kullanılmamışsa, çağrıdaki keyword'ler tek bir Hash olarak son konumsal parametreye gider. Aynı şekilde keyword tanımlamayan bir *args metodu, sona eklenen keyword'leri dizinin son elemanı olarak Hash biçiminde alır.

ruby
def eski_stil(secenekler)
  secenekler
end
p eski_stil(a: 1, "b" => 2) # çıktı: {a: 1, "b" => 2}

def toplayici(*args)
  args
end
p toplayici(1, renk: "mavi") # çıktı: [1, {renk: "mavi"}]

Metot keyword tanımlıyorsa durum değişir. Belgelerdeki örnekte def f(*positional, keyword: nil) metoduna three: 3 gönderildiğinde bu anahtar *positional içine düşmez, unknown keyword: three hatası verir. Eski kodlarda keyword'leri *args üzerinden aktaran metotlar için Ruby 3.2 NEWS dosyası, bu metotların ruby2_keywords ile işaretlenmesi gerektiğini belirtir; yeni kodda ... ya da açık (*args, **kwargs) kullanmak daha doğrudur.

** ile Hash açmak ve değer kısaltması

Çağrı tarafında ** operatörü bir Hash'i keyword argümanlarına açar. Belgelere göre bu operatör yalnızca Hash'lerle değil, to_hash metoduna sahip her nesneyle çalışır; tek istisna nil'dir ve **nil hiçbir anahtar eklemez. Ruby 3.4 NEWS dosyası, çağrılarda **nil desteğinin bu sürümde geldiğini ve **{} gibi davrandığını not eder. Ruby 3.1 ile de keyword argümanlarında değer yazılmayabilir: host: yazdığınızda değer, aynı addaki yerel değişkenden alınır.

ruby
def baglan(host: "localhost", port: 5432)
  "#{host}:#{port}"
end

ayar = { host: "db.local", port: 6000 }
p baglan(**ayar)                   # çıktı: "db.local:6000"
p baglan(port: 7000, **{ host: "x" }) # çıktı: "x:7000"
p baglan(**nil)                    # çıktı: "localhost:5432"

host = "10.0.0.5"
port = 5433
p baglan(host:, port:)             # çıktı: "10.0.0.5:5433"

... ile tüm argümanları yönlendirmek

Bir metot aldığı her şeyi başka bir metoda iletiyorsa, tanımda ve çağrıda ... yazabilirsiniz. Belgelere göre bu yazım Ruby 2.7'den beri var; Ruby 3.0'dan beri ... öncesinde başka argümanlar da kullanılabilir. Tanımda öndeki argümanlar yalnızca varsayılan değersiz konumsal parametre olabilir. Konumsal argümanlar, keyword'ler ve blok birlikte aktarılır.

ruby
def istek(yontem, yol, **basliklar, &blok)
  sonuc = "#{yontem} #{yol} #{basliklar}"
  blok ? blok.call(sonuc) : sonuc
end

def get(...)
  istek(:GET, ...)
end

def logla(mesaj, ...)
  puts "log: #{mesaj}"
  get(...)
end

p get("/api", "Accept" => "json")
# çıktı: "GET /api {\"Accept\" => \"json\"}"
p get("/api", accept: "json") { |s| s.upcase }
# çıktı: "GET /API {ACCEPT: \"JSON\"}"
p logla("deneme", "/saglik")
# çıktı: log: deneme
# çıktı: "GET /saglik {}"

İki kısıt var. Birincisi, ... ile çağrı yalnızca tanımı da ... içeren metotlarda yapılabilir; aksi hâlde Ruby 4.0 unexpected ... when the parent method is not forwarding mesajıyla SyntaxError verir. İkincisi, parantez zorunludur: puts ... satırı yönlendirme değil, sonsuz aralık (endless range) olarak okunur ve Ruby warning: ... at EOL, should be parenthesized? uyarısı verir.

Anonim *, ** ve & yönlendirme

Argümanları yalnızca başka bir metoda aktaracaksanız onlara ad vermeniz gerekmez. Ruby 3.1 NEWS dosyasına göre blok argümanı & anonim olabilir; Ruby 3.2 NEWS dosyasına göre de adsız * ve ** parametreleri artık çağrılarda argüman olarak kullanılabilir. Bu, ... kadar her şeyi değil, yalnızca istediğiniz türü aktarmanızı sağlar.

ruby
def hedef(*a, **k, &b)
  [a, k, b ? b.call : nil]
end

def konum(*)    = hedef(*)
def anahtar(**) = hedef(**)
def blok(&)     = hedef(&)

p konum(1, 2)            # çıktı: [[1, 2], {}, nil]
p anahtar(x: 1)          # çıktı: [[], {x: 1}, nil]
p blok { :blok_calisti } # çıktı: [[], {}, :blok_calisti]

Ruby 3.3 NEWS dosyası bir kısıt ekler: anonim parametre kullanan bir bloğun içinde anonim parametre yönlendirmesi yapılamaz. Karmaşık durumlarda parametrelere ad vermek hem bu kısıttan kaçınmanızı hem de kodun okunmasını kolaylaştırır. Blok tarafının ayrıntıları için blok ve yield yazısına bakabilirsiniz.

Yazımlar bir bakışta

Yazımlar bir bakışta: port: 5432, Tanım, **opts, **nil
YazımNeredeNe yapar
port: 5432TanımVarsayılan değerli keyword
ad:TanımZorunlu keyword
**optsTanımBilinmeyen keyword'leri Hash olarak toplar
**nilTanımMetot keyword kabul etmez
**ayarÇağrıHash'i keyword argümanlarına açar
host:ÇağrıDeğeri aynı addaki değişkenden alır (3.1+)
(...)Tanım ve çağrıTüm argümanları ve bloğu aktarır (2.7+)
(*), (**), (&)Tanım ve çağrıYalnızca ilgili türü adsız aktarır

Sık yapılan hatalar

  • Zorunlu keyword'ü unutmak. missing keyword hatası alırsınız; ya varsayılan değer verin ya da çağrıyı düzeltin.
  • Yazım hatalı anahtar göndermek. Metot ** ile toplamıyorsa unknown keyword hatası oluşur. Bu aslında yararlı bir korumadır; her şeyi **opts ile toplamak yazım hatalarını gizleyebilir.
  • Ruby 2 alışkanlığıyla son Hash'in keyword'e dönüşmesini beklemek. Ruby 3.0'dan beri Hash ve keyword ayrıdır; Hash'i keyword olarak vermek için çağrıda **hash yazın.
  • ... çağrısını parantezsiz yazmak. puts ... yönlendirme yapmaz, uyarı verir.
  • Değer kısaltmasını parantezsiz kullanmak. Sonraki satır yanlışlıkla değer olarak okunabilir.

Sık Sorulan Sorular

Keyword argümanları ile Hash argümanı arasındaki fark nedir?

Keyword argümanları metodun tanımında tek tek adlandırılır; Ruby eksik ve bilinmeyen anahtarları sizin yerinize kontrol eder. Hash argümanı ise tek bir konumsal parametredir ve içeriğini metot kendisi okur. Ruby 3.0'dan beri bu ikisi dil düzeyinde ayrıdır.

Keyword argümanlarının sırası önemli mi?

Hayır. Belgelere göre keyword argümanları çağrıda herhangi bir sırayla verilebilir. Sıra yalnızca türler arasında önemlidir: konumsal argümanlar önce, keyword'ler sonra, blok en sonda gelir.

... ile *args, **kwargs, &block yazmak arasında fark var mı?

Amaç aynıdır: tüm argümanları başka bir metoda aktarmak. ... daha kısadır ama aktarılan argümanlara metot içinde ad ile erişemezsiniz. Argümanları okumanız ya da değiştirmeniz gerekiyorsa adlı parametreler kullanın.

Bir lambda keyword argümanı alabilir mi?

Evet, lambda parametreleri metot parametreleri gibi davranır. Lambda ile proc arasındaki argüman farkları için Proc ve lambda farkı yazısına bakın.

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 · Metot tanımlama · Blok ve yield · Hash metotları · Proc ve lambda farkı