İçeriğe geç
academia.sh

Ders 05 / 12

Yöntemler

Alıcısız çağrıların Kernel modülünde bulunması; varsayılan değer, değişken sayıda argüman ve anahtarlı argümanlı yöntem tanımı; örtük dönüş; üst düzey def'in Object'e yazılması ve özel olması.

Yayın Güncelleme

Bu derste

  • Alıcısız çağrıların yanıtlayanını method ve owner ile bulur
  • Varsayılan değer, değişken sayıda argüman ve anahtarlı argümanı bir yöntem tanımında birlikte kullanır
  • Üst düzey def ile tanımlanan yöntemin sahibinin Object olduğunu ve özel kaldığını ölçer
İçindekiler

Önceki ders case‘in dal seçerken dal nesnesine bir ileti gönderdiğini gösterdi ve bir soru bıraktı: format("%03d", 7) ya da puts "ad" yazıldığında ortada bir alıcı yok. İleti nereye gidiyor, onu kim yanıtlıyor? Fonksiyon kavramı Programlama Temelleri kursunda (M01/K02) tanıtılmıştı; burada o kavram tekrarlanmaz. Bu ders Ruby’nin yöntemlerini tek bir ölçüyle okur: alıcısız bir çağrıyı karşılayan sahip kim?

Alıcısız Çağrıyı Kim Karşılar

format("%03d", 7) ifadesinde nokta yok, alıcı yok. Ama önceki derslerde öğrendik: Ruby’de her çağrı bir iletidir ve iletinin bir alıcısı vardır. Alıcı yazılmadığında alıcı, kodun o anda çalıştığı nesnedir. Üst düzeyde bu nesne dosyanın ana nesnesidir. Aşağıdaki dosya bu çağrının yöntem olduğunu defined? ile, sahibini method ve owner ile sorar.

# alicisiz.rb — alicisiz cagrinin yanitlayani
puts defined?(format("%03d", 7))
puts method(:format).owner
puts method(:format).owner.instance_of?(Class)
puts format("%03d", 7)
method
Kernel
false
007

format bir yöntemdir ve sahibi Kernel’dir. Kernel bir Class değil, bir modüldür, bu yüzden instance_of?(Class) false döndü. Kursun sınıflandırma kuralı bu durumda yanıtlayanı modül sayar. Dosyanın üst düzeyindeki nesne de Kernel yöntemlerine ulaşır, o yüzden alıcı yazılmasa da çağrı bir sahip bulur.

DT12. Bu derste “yöntem”, def ile ya da dil tarafından tanımlanmış, adıyla çağrılan iş birimidir. method(:ad).owner yöntemin sahibini, yani tanımının bulunduğu sınıf ya da modülü verir. Arama zincirinin kendisi (hangi sırayla bakıldığı) bu kursta ölçülmez; yalnız sonuç, yani sahip basılır.

Kernel’deki Tanıdık Çağrılar

Önceki derslerde puts ve p hiçbir açıklama yapılmadan kullanıldı. Aşağıdaki dosya altı çağrının hepsinin sahibini sorar.

# kernel_cagrilari.rb — alicisiz cagrilarin sahibi
[:puts, :p, :format, :Integer, :raise, :block_given?].each do |ad|
  printf("%-14s %s\n", ad, method(ad).owner)
end
puts           Kernel
p              Kernel
format         Kernel
Integer        Kernel
raise          Kernel
block_given?   Kernel

Altısı da Kernel’dedir. puts ve p ekrana yazar, format biçimler, Integer dönüştürür, raise bir istisna fırlatır, block_given? ise yöntemin bir bloğu olup olmadığını söyler. Hepsi “dil sözcüğü” gibi görünür ama aslında sıradan bir yöntemdir ve sahibi bellidir. Bu, önceki dersteki if ve while’dan farklıdır: onlar sözdizimiydi, bunlar iletidir.

Parametre Türleri ve Örtük Dönüş

Kendi yöntemimizi def ile yazarız. Parametre listesinde zorunlu argümanlar, varsayılan değerler, değişken sayıda argüman ve anahtarlı argümanlar bir arada bulunabilir. Yöntem return yazılmasa da son ifadenin değerini döndürür; buna örtük dönüş denir.

# parametreler.rb — parametre turleri ve ortuk donus
def birlestir(ad, unvan = "bay", *ekler, sehir: "ankara")
  "#{unvan} #{ad} #{ekler.size} #{sehir}"
end

puts birlestir("ali")
puts birlestir("ali", "dr", 1, 2, sehir: "izmir")
p method(:birlestir).parameters
bay ali 0 ankara
dr ali 2 izmir
[[:req, :ad], [:opt, :unvan], [:rest, :ekler], [:key, :sehir]]

İlk çağrıda yalnız ad verildi; unvan ve sehir varsayılan değerlerini aldı, ekler boş kaldı. İkinci çağrıda "dr" varsayılanı ezdi, 1 ve 2 değişken sayıda argüman olarak ekler dizisine toplandı ve sehir: anahtarlı argüman olarak verildi. parameters her birinin türünü etiketler: zorunlu req, varsayılanlı opt, değişken sayıda rest, anahtarlı key. Yöntemde return yok; son ifade olan dizgi döndü. Dizgideki #{...} yazımı ifade yerleştirmedir.

Blok Alan Yöntemler

Bir yöntem çağrısına süslü parantez ya da do ... end ile bir blok verilebilir. Yöntem bloğu yield ile çalıştırır, bloğun verilip verilmediğini block_given? ile sorar. block_given?’ın sahibi iki bölüm önce Kernel çıkmıştı: bu da sıradan bir yöntemdir. Bloğu olmayan çağrıda yöntem farklı bir değer döndürebilir. Ayrıca zorunlu argüman eksik verilirse ne olacağı da ölçülebilir.

# blok_alan.rb — yield ve block_given?
def tekrar(adet)
  return :bos unless block_given?
  adet.times { |sira| yield sira }
  :bitti
end

p tekrar(2) { |sira| puts sira }
p tekrar(2)

begin
  tekrar
rescue ArgumentError => e
  puts e.class
end
0
1
:bitti
:bos
ArgumentError

Blok verilen çağrıda blok iki kez çalıştı (0 ve 1 yazıldı) ve yöntem :bitti döndürdü. Blok verilmeyen çağrıda block_given? false olduğu için yöntem :bos ile erken döndü. Zorunlu adet argümanı verilmeyen çağrı ArgumentError fırlattı ve yalnız sınıfı basıldı. Yani bir yöntemin arayüzü yalnız parametre listesi değildir; blok da çağrının parçasıdır, ama bu yöntemin parametre listesinde görünmez. Blok &blok biçiminde açıkça adlandırılırsa listeye girer; bunu sonraki bölümün :block etiketi gösterir.

Birden Çok Değer Döndürmek

Ruby’de bir yöntem tek değer döndürür, ama o değer bir dizi olabilir ve çağıran taraf diziyi tek hamlede parçalara ayırabilir. Bu da örtük dönüşle birlikte çalışır: yöntemin son ifadesi köşeli parantezli iki elemanlı bir dizidir.

# cok_donus.rb — dizi donduren yontem ve coklu atama
def bol(a, b)
  [a / b, a % b]
end

sonuc = bol(17, 5)
p sonuc
bolum, kalan = bol(17, 5)
puts bolum
puts kalan
puts sonuc.class
[3, 2]
3
2
Array

Yöntem tek bir Array döndürdü. bolum, kalan = ... yazımı bu diziyi iki adda açar; bu bir yöntem özelliği değil, atamanın yazımıdır ve önceki dersteki sözdizimi gözlemine bağlanır. Böylece yöntemin dönüş değeri ile çağıranın bunu nasıl okuduğu iki ayrı iş olarak kalır.

Parametre Türlerini Yoklamak

Bu bölüm parametre türlerini ölçer. Aşağıdaki yöntemde yedi parametre türü bir arada durur: zorunlu, varsayılanlı, artan (*rest), zorunlu anahtarlı, isteğe bağlı anahtarlı, anahtar toplayan (**opts) ve blok alan (&blok). Ölçü, yöntemin kendi kendini anlatmasıdır: method(:ad).parameters yöntemin parametre listesini, arity ise kaç argüman beklediğini bildirir. Eksen sahiptir; tanımlanan yöntemin sahibi de basılır.

# parametre_turleri.rb — parametre turleri ve arity
def ornek(a, b = 2, *rest, c:, d: 4, **opts, &blok); end
puts method(:ornek).parameters.inspect
puts method(:ornek).arity
def iki(a, b); end
puts method(:iki).arity
def secenekli(a, b = 1); end
puts method(:secenekli).arity
puts method(:ornek).owner
[[:req, :a], [:opt, :b], [:rest, :rest], [:keyreq, :c], [:key, :d], [:keyrest, :opts], [:block, :blok]]
-3
2
-2
Object

parameters çıktısı her parametreyi bir tür etiketiyle verdi: :req zorunlu, :opt varsayılanlı, :rest artan, :keyreq zorunlu anahtarlı, :key isteğe bağlı anahtarlı, :keyrest anahtar toplayan, :block blok alan parametredir. arity değerleri üç ayrı durumu gösterdi: iki zorunlu parametreli iki için 2, bir zorunlu ve bir varsayılanlı secenekli için -2, ornek için -3. Negatif sayı “sabit bir sayıda argüman değil” demektir; isteğe bağlı ya da sınırsız parametre vardır. Mutlak değer, en az gereken argüman sayısının bir fazlasıdır: -2 en az bir, -3 en az iki argüman ister. ornek’te zorunlu a’nın yanında zorunlu anahtarlı c: ve isteğe bağlı parametreler bulunur ve sonuç -3 oldu. Zorunlu anahtarlı parametre bu hesapta tek bir ek argüman sayılır. Kural burada yalnız bu üç yöntemde ölçüldü. Son satırda ornek’in sahibi Object çıktı: üst düzeyde tanımlanan yöntemin yanıtlayanı Object’tir.

DT13. arity değerinin işareti yalnız bu üç yöntemde okundu. Başka parametre dizilimleri için aynı kuralın geçtiği ölçülmedi.

# anahtar_hatasi.rb — eksik ve fazla anahtar ile ortuk donus
def kutu(en:, boy: 1)
  en * boy
end
begin
  kutu(boy: 2)
rescue ArgumentError => e
  puts e.class
end
begin
  kutu(en: 1, derinlik: 3)
rescue ArgumentError => e
  puts e.class
end
puts kutu(en: 3)
def dolayli
  42
end
def acik
  return 42
end
puts dolayli == acik
def erken(n)
  return :negatif if n < 0
  :pozitif
end
puts erken(-1).inspect
puts erken(1).inspect
puts method(:kutu).owner
ArgumentError
ArgumentError
3
true
:negatif
:pozitif
Object

kutu(boy: 2) zorunlu en: anahtarı olmadan çağrıldı ve ArgumentError fırlattı; kutu(en: 1, derinlik: 3) tanımsız bir anahtar verildiği için yine ArgumentError fırlattı. İki durumda da yalnız e.class basıldı, çünkü hata iletisinin metni sürüme bağlı kalabilir. Geçerli çağrı kutu(en: 3) 3 verdi. Bu üç sonuç anahtarlı parametrelerin bir sözleşme olduğunu gösterir: eksik de fazla da aynı sınıfla reddedilir. İkinci yarıda örtük dönüşle return karşılaştırıldı: dolayli ile acik aynı 42 değerini döndürdü. return gereksiz değildir; erken(-1) satırı return’ün yöntemi erken bitirdiğini, erken(1) satırı ise son ifadenin değerinin dönüş değeri olduğunu gösterir. Yöntemin sahibi kutu için de Object çıktı. Böylece dönüş değeri iki yoldan belirlenir: yöntemin son ifadesi ya da return ile verilen değer.

Yöntemi Nesne Olarak Tutmak

Yöntem de bir nesneye dönüştürülebilir. method(:ad) çağrısı yöntemi Method sınıfından bir nesne olarak verir; bu nesne çağrılabilir, bir bloğa dönüştürülebilir ve alıcısı ile sahibi sorulabilir. Bu bölümün sorusu, üst düzeyde tanımlı bir yöntemin kime ait olduğudur ve dışarıdan sorulduğunda herkese açık görünüp görünmediğidir. Eksen sahiptir; alıcıyı ve sahibi birlikte basıyoruz.

# yontem_nesnesi.rb — yontemi nesne olarak tutmak
def kare(n)
  n * n
end
m = method(:kare)
puts m.class
puts m.call(4)
puts [1, 2, 3].map(&m).inspect
puts m.name.inspect
puts m.receiver == self
puts m.owner
puts send(:kare, 3)
puts respond_to?(:kare, true)
puts respond_to?(:kare)
Method
16
[1, 4, 9]
:kare
true
Object
9
true
false

method(:kare) bir Method nesnesi verdi. m.call(4) 16 döndürdü; yöntem bir nesne olduğu için bir değişkende tutulup sonra çağrılabildi. [1, 2, 3].map(&m) ifadesi & ile yöntem nesnesini bloğa dönüştürdü ve [1, 4, 9] verdi. Ad :kare bir semboldür. m.receiver == self true oldu: üst düzeyde tanımlanan yöntemin alıcısı, üst düzeydeki main nesnesidir ve m.owner Object çıktı. Böylece alıcısız bir kare(3) çağrısının neden çalıştığı da açıklanır: alıcı yazılmadığında ileti, o anki self nesnesine gider.

Son üç satır erişim farkını ölçer. send(:kare, 3) 9 verdi, yani iletiyi adıyla göndermek çalıştı. respond_to?(:kare, true) true, respond_to?(:kare) ise false bastı. İkinci argüman özel yöntemleri de saymayı söyler; argümansız soru yalnız herkese açık yöntemleri sayar ve üst düzey yöntem bunların arasında değildir. Yani kare, Object sınıfında tanımlıdır ve herkese açık yöntemler arasında sayılmaz, ama alıcısız çağrıyla ya da send ile ulaşılabilir. Alıcı yazılarak yapılan bir çağrının ne verdiği burada ölçülmedi. Bu fark Object sahibinin ve erişim düzeyinin ayrı iki özellik olduğunu gösterir.

DT14. Üst düzey yöntemin özel olduğu respond_to? çiftinden çıkarıldı; erişim düzeyi başka bir ölçümle (private_methods gibi) doğrulanmadı.

Çağrı Tarafında Splat

Parametre tanımında görülen * ve ** işaretleri, çağrı tarafında tersini yapar: bir diziyi ya da karma yapıyı argüman listesine açarlar. Bu bölüm iki yönü birlikte ölçer. Eksen sahiptir; bir yöntemin kaç argüman bekleyebileceği arity ile, kimin tanımladığı owner ile basılır.

# splat_cagri.rb — cagri tarafinda * ve **
def topla(*sayilar)
  sayilar.sum
end
dizi = [1, 2, 3]
puts topla(*dizi)
puts topla(dizi.first, *dizi.last(2))
puts topla
def secenek(**o)
  o.inspect
end
h = {a: 1}
puts secenek(**h)
puts secenek(b: 2, **h)
def tek(x)
  x
end
puts tek([1, 2]).inspect
puts method(:topla).arity
puts method(:topla).owner
6
6
0
{a: 1}
{b: 2, a: 1}
[1, 2]
-1
Object

topla(*dizi) diziyi üç ayrı argümana açtı ve 6 verdi. topla(dizi.first, *dizi.last(2)) yazımı bir tek argümanla açılmış iki argümanı birleştirdi ve yine 6 çıktı. Argümansız topla çağrısı 0 verdi: artan parametre boş bir diziyi kabul eder. secenek(**h) karma yapıyı anahtarlı argümanlara açtı ve {a: 1} verdi; secenek(b: 2, **h) açık yazılmış bir anahtarla açılmış anahtarı birleştirdi ve {b: 2, a: 1} oldu. Birleşmede açık yazılan anahtar önce geldi.

Karşıt örnek tek([1, 2]) çağrısıdır: yıldız olmadan verilen dizi tek argüman olarak kaldı ve yöntem [1, 2] döndürdü. Yani açma işlemi çağrı tarafında bilerek yazılır; yıldız olmayan yerde dizi bir dizidir. method(:topla).arity -1 verdi: sabit sayıda argüman beklenmez ve en az sıfır tanesi gerekir. Sahip Object çıktı. Bir çağrının argümanlarını dilin yazımı açar, ama açılmış listeyi karşılayan yine yöntemin parametre tanımıdır; iki taraf birbirine uyduğunda çağrı çalışır. Parametre tanımı ve çağrı tarafı aynı işareti kullanır, ama yönleri zıttır: tanımda * argümanları toplar, çağrıda * diziyi açar.

Sınırlayıcı Ölçüm

Bu dersin yanıtlayanı modüldür, ama bir kayma var: kendi yazdığımız yöntemin sahibi bir modül mü olur? Dosyanın üst düzeyinde def yazıldığında yöntem nereye gider? Aşağıdaki ölçüm bunu sorar.

# ust_duzey.rb — ust duzey def Object'e yazilir ve ozeldir
def kare(n)
  n * n
end

puts method(:kare).owner
puts method(:kare).owner.instance_of?(Class)
puts respond_to?(:kare)
puts respond_to?(:kare, true)
puts kare(4)
Object
true
false
true
16

Üst düzey def kare yöntemi Object sınıfına yazar. Object bir Class örneğidir (true), yani bu durumda yanıtlayan modül değil, sınıftır. Yöntem aynı zamanda özel kalır: respond_to?(:kare) false döndü, çünkü özel yöntemler dışarıdan sorulduğunda görünmez. respond_to?(:kare, true) özel yöntemleri de kapsar ve true verir. Alıcısız kare(4) çağrısı ise 16 döndürdü, çünkü alıcı yazılmadığında özel yöntemlere de erişilir. Kernel’deki yöntemler modülün sahipliğindedir; kendi def’imiz ise Object’in, yani bir sınıfın sahipliğindedir.

Özet

  • Alıcısız çağrıda alıcı, kodun o anda çalıştığı nesnedir; format("%03d", 7) çağrısını Kernel modülü yanıtlar.
  • puts, p, format, Integer, raise ve block_given? sıradan yöntemlerdir ve altısının da sahibi Kernel’dir.
  • Bir yöntem zorunlu, varsayılan değerli, değişken sayıda ve anahtarlı argümanı birlikte taşıyabilir; return yazılmazsa son ifade döner.
  • Üst düzey def yöntemi Object sınıfına yazar ve özel bırakır; respond_to?(:kare) false, respond_to?(:kare, true) true döner.
  • Bu dersin yanıtlayanı modüldür: alıcısız çağrıların sahibi Kernel modülüdür, kendi yazdığımız üst düzey yöntemin sahibi ise sınıf olan Object’tir.
  • method(:ad).parameters ölçülen yöntemdeki yedi parametre türünü adlandırır; eksik ya da tanımsız anahtar ArgumentError verir; çağrı tarafında * ve ** diziyi ve karma yapıyı argümanlara açar.

Sonraki Adım

Yöntemlerin sahibi ölçüldü ve alıcısız çağrının nereye gittiği görüldü. Sırada dizgiler var: "ad".to_sym yazıldığında dizgi bir sembole dönüşüyor ve sembol de bir nesne. Dizgi sembole dönüşünce iletiyi kim yanıtlıyor, ve bir sihirli yorum bunu değiştiriyor mu? Sonraki ders, Semboller ve Dizgiler, bunu ölçer.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat