İçeriğe geç
academia.sh

Ders 10 / 12

Proc ve Lambda

Bir bloğun nesneye dönüştürülmesinin iki yolu olan proc ve lambda için call iletisini bir sınıfın yanıtladığı, farkın argüman denetiminde ve lambda? durumunda yattığı ölçülür.

Yayın Güncelleme

Bu derste

  • Bir bloğu proc ya da lambda olarak bir değişkene atar ve call ile çağırır
  • Proc ve lambda arasındaki argüman denetimi farkını ölçer
  • lambda? ile bir nesnenin hangi türde olduğunu sorar
  • call yönteminin sahibinin iki tür için de aynı olduğunu ölçer
İçindekiler

Önceki iki derste blok yalnız bir yöntemin çağrısına eşlik eden kod parçasıydı: yazıldığı yerde kaldı, bir değişkende doğrudan saklanamadı. Proc, bloğu bir nesneye dönüştürür; lambda ise aynı nesnenin daha kati davranan biçimidir. İkisinin de sınıfı Proc‘tur. Bir proc, tanımlandığı kapsamdaki değişkenleri taşır; bu özelliğe M01/K02’de kapanış (closure) denmişti ve burada yeniden tanımlanmaz.

Bu dersin yanıtlayanı sınıftır: call iletisini Proc sınıfı yanıtlar. Fark ise sınıfta değil, nesnenin lambda? durumundadır.

Çağırmanın Üç Yolu

Lambda -> ile yazılır. Çağırmak için call, nokta-parantez kısayolu .() ya da köşeli parantez kullanılır; üçü aynı iletidir. Çekirdek bu çağrıyı sınıflandırır: call’ın sahibi bir Class ise yanıtlayan sınıftır.

# yanit.rb — bir iletiyi kimin yanitladigini siniflandiran yardimcilar

# Siniflandirma sirayla sorar: once ifade bir ileti mi (defined?),
# sonra iki farkli blok iki farkli sonuc veriyor mu, sonra yontemin sahibi kim.
def yanitlayan(tanim, sahip: nil, sonuc: nil, bloklu: nil)
  return ["sozdizimi", "defined? #{tanim}"] unless tanim == "method"

  if bloklu
    birinci, ikinci = bloklu.call
    return ["blok", "#{birinci.inspect} / #{ikinci.inspect}"] unless birinci == ikinci
  end

  tur = sahip.instance_of?(Class) ? "sinif" : "modul"
  [tur, "#{sahip}, sonuc #{sonuc.inspect}"]
end
# lambda.rb — lambda cagrisini kim yanitliyor
require_relative "yanit"

iki_kat = ->(n) { n * 2 }
sahip = iki_kat.method(:call).owner
tur, kanit = yanitlayan(defined?(iki_kat.call(3)), sahip: sahip, sonuc: iki_kat.call(3))
puts "#{tur}  #{kanit}"
puts iki_kat.class
puts iki_kat.call(3)
puts iki_kat.(3)
puts iki_kat[3]
sinif  Proc, sonuc 6
Proc
6
6
6

İlk satır çekirdeğin lambda cagrisi gözlemidir: yanıtlayan sınıf, sahip Proc. Üç çağrı biçimi de 6 verdi. KB11. Üç biçim bu derste aynı sonuçla ölçüldü; hangisinin tercih edildiği bir yazım tercihidir ve ölçülmez.

Proc ile Lambda Arasındaki Fark

Fark ilk bakışta görünmez: ikisi de çağrılır, ikisi de değer döndürür. Ayrım argüman denetiminde ortaya çıkar. Lambda tam sayıda argüman bekler; proc ise fazlayı yok sayar, eksiği nil ile doldurur. Hata durumunda lambda’nın fırlattığı istisnanın sınıfı basılır, iletisi değil.

# fark.rb — proc ve lambda arasindaki arguman farki
gevsek = proc { |x| x }
kati = lambda { |x| x }
puts gevsek.call(1, 2)

sonuc = begin
  kati.call(1, 2)
rescue ArgumentError => hata
  hata.class
end
puts sonuc

cift = proc { |x, y| [x, y] }
puts cift.call(1).inspect
puts "gevsek=#{gevsek.lambda?} kati=#{kati.lambda?}"
1
ArgumentError
[1, nil]
gevsek=false kati=true

Proc iki argümanı birden verildiğinde fazlayı bıraktı ve 1 döndürdü; lambda aynı çağrıda ArgumentError fırlattı. İki parametreli proc’a tek argüman verilince eksik parametre nil oldu. lambda? ikisini ayırır: proc için false, lambda için true.

Başka Nesnelerden Proc

Ruby’de başka kaynaklardan da proc elde edilir: bir yöntem nesnesinden to_proc, bir sembolden to_proc. İkisi de lambda gibi davranır.

# donusum.rb — yontem ve sembol proc'a donusur
def kare(n)
  n * n
end

puts method(:kare).to_proc.lambda?
puts :upcase.to_proc.lambda?
puts :upcase.to_proc.call("a")
true
true
A

Yöntemden ve sembolden elde edilen proc’ların lambda? değeri true. :upcase.to_proc.call("a") büyük harfli dizgiyi verdi. Bu, önceki derste görülen select(&:even?) biçiminin arkasındaki düzenektir: & sembolü bir proc’a çevirir ve bloğun yerine verir.

Değişkeni Taşımak

Bir proc, tanımlandığı kapsamdaki değişkenlere bağlı kalır. Bunu görmek için bir yöntem bir lambda döndürür ve lambda, yöntem bittikten sonra bile değişkeni artırmaya devam eder.

# sayac.rb — lambda tanimlandigi yerdeki degiskeni tasir
def sayac_uret
  adet = 0
  -> { adet += 1 }
end

birinci = sayac_uret
ikinci = sayac_uret
puts birinci.call
puts birinci.call
puts ikinci.call
puts birinci.call
1
2
1
3

Her sayac_uret çağrısı kendi adet değişkenini kurdu. birinci sırayla 1, 2 ve 3 verdi; ikinci kendi sayacından 1 verdi. Yöntem bitmişti, ama lambda değişkeni taşımaya devam etti. KB13. Bu ölçüm iki bağımsız sayacı gösterir; aynı değişkeni paylaşan iki lambda bu derste ölçülmez.

Nesneyi Blok Yerine Vermek

Bir proc ya da lambda, başına & konarak bir yönteme blok olarak verilebilir. Bu, Bloklar dersindeki map ile bu dersin nesnelerini birleştirir. arity ise nesnenin kaç parametre beklediğini sayı olarak verir.

# blok_yerine.rb — nesneyi blok olarak vermek ve arity
iki_kat = ->(n) { n * 2 }
gevsek = proc { |x, y| [x, y] }
puts [1, 2, 3].map(&iki_kat).inspect
puts "arity lambda=#{iki_kat.arity} proc=#{gevsek.arity}"
puts [[1, 2], [3, 4]].map(&gevsek).inspect
[2, 4, 6]
arity lambda=1 proc=2
[[1, 2], [3, 4]]

map(&iki_kat) her elemanı lambda’ya verip [2, 4, 6] topladı: ayrı bir blok yazmak gerekmedi. arity lambda için 1, iki parametreli proc için 2 döndü. İç içe dizilerde proc her alt diziyi iki parametreye bölüp [x, y] dizisini yeniden kurdu, bu yüzden sonuç girdiyle aynı göründü. KB14. Bu ölçümde lambda her zaman tek elemanlı çağrıldı; lambda’ya & ile iç içe dizi verildiğinde ne olduğu ölçülmedi.

return Nerede Döner

Proc ile lambda arasındaki bir fark da return’ün nereye döndüğüdür. Aşağıdaki iki yöntem aynı işi yapar: içeride bir nesne kurar, çağırır ve ardından 2 döndürmeye çalışır.

# donus_farki.rb — proc ve lambda icinde return
def proc_ile
  p = proc { return 1 }
  p.call
  2
end

def lambda_ile
  l = lambda { return 1 }
  l.call
  2
end

puts proc_ile
puts lambda_ile
1
2

proc_ile 1 döndürdü: proc içindeki return, proc’u değil onu çevreleyen yöntemi bitirdi ve 2 satırına hiç ulaşılmadı. lambda_ile ise 2 döndürdü: lambda içindeki return yalnız lambda’yı bitirdi, yöntem kaldığı yerden sürdü. KB30. Ölçüm yalnız yöntem içinde yapıldı; üst düzeyde bir proc içindeki return bu derste denenmedi.

Kısmi Uygulama

curry, çok parametreli bir lambda’yı her çağrıda bir parametre alan bir zincire çevirir. Parametrelerin bir kısmı verilince geriye kalanı bekleyen yeni bir nesne döner.

# curry.rb — curry ile parametre parametre cagirma
topla = ->(a, b, c) { a + b + c }
puts topla.curry[1][2][3]
puts topla.curry.(1, 2).(3)
kismi = topla.curry[1]
puts kismi.class
puts kismi.lambda?
6
6
Proc
true

Üç köşeli parantezle ve (1, 2).(3) biçimiyle curry aynı sonucu, 6‘yı verdi. topla.curry[1] bir Proc döndürdü ve bu nesne hâlâ bir lambda’ydı. KB31. curry yalnız sabit parametreli bir lambda ile ölçüldü.

Yöntem Nesnesi

method(:ad) bir yöntemi nesneye çevirir. Bu nesne çağrılabilir, sahibi sorulabilir ve & ile bloğa dönüştürülebilir.

# yontem_nesnesi.rb — method ile Method nesnesi ve to_proc
def kare(n) = n * n
m = method(:kare)
puts m.class
puts m.call(4)
puts "sahip=#{m.owner} arity=#{m.arity}"
puts [1, 2, 3].map(&m).inspect
puts m.to_proc.class
Method
16
sahip=Object arity=1
[1, 4, 9]
Proc

method(:kare) bir Method nesnesi verdi ve call(4) 16 döndürdü. Üst düzeyde tanımlanan kare’nin sahibi Object olarak basıldı. map(&m) yöntemi blok yerine verdi ve [1, 4, 9] üretti; to_proc ise bir Proc döndürdü. KB32. Ölçüm yalnız üst düzeyde tanımlı bir yöntemle yapıldı.

Blok ile Proc Arasındaki Dönüşüm

Bir yöntemin son parametresi &blok ise, kendisine verilen blok bir Proc nesnesine çevrilir. Tersine, bir lambda & ile bloğun yerine verilebilir. Aşağıdaki ölçüm, bu iki yönde nesnenin kimliğinin korunup korunmadığına bakar.

# blok_proc.rb — blok ve lambda arasindaki donusum
def al(&blok) = blok
b = al { |x| x + 1 }
puts b.class
puts b.lambda?
puts b.call(1)
l = ->(x) { x + 1 }
puts al(&l).equal?(l)
puts al(&l).lambda?
Proc
false
2
true
true

Bloktan dönen nesne bir Proc oldu ve lambda? false verdi. Bir lambda &l ile verilince ise al aynı nesneyi geri döndürdü: equal? true ve lambda? hâlâ true. Yani & bir lambda’yı proc’a dönüştürmedi. KB33. Ölçüm yalnız tek parametreli bir blok ve bir lambda ile yapıldı.

Çağrılabilir Nesneleri Birleştirmek

>> ve << iki çağrılabilir nesneyi tek bir Proc içinde birleştirir. f >> g önce f’yi sonra g’yi uygular; f << g ise tersini yapar. Method nesnesi de aynı birleştirmeye katılabilir.

# birlestir.rb — proc birlestirme
art = ->(x) { x + 1 }
iki = ->(x) { x * 2 }
puts (art >> iki).call(3)
puts (art << iki).call(3)
puts (art >> iki).class
puts (method(:Integer) >> art).call("4")
8
7
Proc
5

(art >> iki).call(3) önce art’ı sonra iki‘yi uyguladı: (3 + 1) * 2 = 8. (art << iki).call(3) sırayı ters çevirdi: art(iki(3)) = 7. Birleştirme sonucu bir Proc oldu. method(:Integer) >> art bir Method nesnesini lambda ile birleştirdi: "4" önce 4 oldu, sonra 5 verdi. KB45. Ölçüm tek parametreli lambda’larla yapıldı.

arity ve parameters

arity bir çağrılabilir nesnenin kaç parametre beklediğini, parameters ise her birinin türünü bildirir. Proc ile lambda aynı parametre listesini farklı bildirebilir.

# arity.rb — arity ve parameters
puts proc { |a, b| }.arity
puts lambda { |a, b| }.arity
puts proc { |*a| }.arity
puts lambda { |a, b = 1| }.arity
puts lambda { |a, b = 1| }.parameters.inspect
puts proc { |a, b| }.parameters.inspect
2
2
-1
-2
[[:req, :a], [:opt, :b]]
[[:opt, :a], [:opt, :b]]

proc { |a, b| } ve lambda { |a, b| } ikisi de 2 bildirdi. *a ile -1, bir zorunlu ve bir isteğe bağlı parametreyle -2 basıldı: iki ölçümde de negatif değer, zorunlu parametre sayısının bir fazlasının eksilisiydi. parameters farkı gösterir: lambda’da a zorunlu (:req), proc’ta aynı parametre isteğe bağlı (:opt). Proc ile Lambda Arasındaki Fark bölümündeki gevşeklik böylece nesnenin kendi tanımında okunur. KB46.

Kapanış Değişkeni Paylaşır

Lambda, dıştaki yerel değişkenin değerini değil değişkenin kendisini taşır. Değişken sonradan değişirse lambda yeni değeri görür; lambda değişkene yazarsa dıştaki değişken de değişir.

# paylasim.rb — kapanis ve dis degisken
x = 1
oku = -> { x }
x = 5
puts oku.call
yaz = -> { x = 9 }
yaz.call
puts x
5
9

oku tanımlandığında x 1’di; x sonradan 5 yapıldı ve oku.call 5 verdi. yaz içindeki x = 9 dıştaki x’e yazdı ve puts x 9 bastı. Lambda bir kopya değil, tanımlandığı yerdeki değişkeni kullanır. KB47. Ölçüm üst düzeydeki yereller içindir; yöntem sınırını geçen durum Değişkeni Taşımak bölümünde ölçüldü.

case ile Lambda

case ifadesi when koşulunu === ile sınar. Bir lambda, Proc#=== sayesinde bu sınamaya doğrudan katılabilir: === lambda’yı verilen değerle çağırır.

# case_lambda.rb — when kolunda lambda
cift = ->(n) { n.even? }
[1, 2].each do |n|
  case n
  when cift then puts "#{n} cift"
  else puts "#{n} tek"
  end
end
puts cift === 2
puts "sahip=#{cift.method(:===).owner}"
1 tek
2 cift
true
sahip=Proc

when cift lambda’yı her n için çağırdı: 1 için false döndüğünden else koştu, 2 için true döndüğünden when kolu koştu. cift === 2 doğrudan true verdi ve ===‘ün sahibi Proc olarak basıldı. Böylece case, koşulu sınarken lambda’ya === iletisini gönderir ve yanıtlayan bir sınıftır. Bu, Proc ve Lambda dersinin ortak sorusuna bağlanır: aynı çağrılabilir nesne call ile, .() ile, & ile blok yerine ve burada === ile kullanılabilir; her birinde nesnenin kendisi aynıdır, yalnız ileti değişir. KB54. Ölçüm tek parametreli bir lambda ile yapıldı; çok parametreli lambda’nın when kolunda davranışı denenmedi. === kolunun değeri case’in kendi sınamasında kullanıldığı için bu ölçümde ayrıca bir sonuç değişkenine atanmadı.

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

Proc ile lambda arasındaki farkı call’ın sahibine bakarak görebilir miyiz? Hayır: iki tür için de sahip aynıdır, çünkü ikisi de Proc sınıfının örneğidir. Fark sahipte değil, nesnenin durumundadır.

# sahip.rb — call iletisinin sahibi iki turde de ayni
gevsek = proc { |x| x }
kati = lambda { |x| x }
puts "proc    #{gevsek.method(:call).owner} lambda?=#{gevsek.lambda?}"
puts "lambda  #{kati.method(:call).owner} lambda?=#{kati.lambda?}"
proc    Proc lambda?=false
lambda  Proc lambda?=true

Sahip iki satırda da Proc; yalnız lambda? farklı. Önceki derslerde owner sorusu bir yöntemin hangi sınıfta ya da modülde tanımlı olduğunu ayırıyordu; burada iki nesne aynı sınıftan olduğu için aynı soru bir farkı ortaya çıkarmıyor. Aynı iletiyi iki nesneye göndermek iki ayrı yanıtlayanı değil, tek yanıtlayanın iki durumunu veriyor. Yani owner sorusu, proc ile lambda arasındaki farkı göstermez: sınıf aynıdır, farkı nesnenin kendi durumu taşır. KB12. Bu ölçüm yalnız call iletisini sorar; başka iletilerde sahip farkı olup olmadığı bu derste ölçülmez.

Özet

  • Proc ve lambda, bir bloğu nesneye çeviren iki yoldur; ikisinin de sınıfı Proc’tur.
  • Bu dersin yanıtlayanı sınıftır: call iletisini Proc yanıtlar ve call, .() ve köşeli parantez aynı sonucu verir.
  • Lambda argüman sayısını denetler ve fazlalıkta ArgumentError fırlatır; proc fazlayı bırakır, eksiği nil yapar.
  • Yöntem nesnesinden ve sembolden elde edilen proc’lar lambda? bakımından true döner.
  • Sınırlayıcı ölçüm: call sahibi iki tür için de Proc’tur; fark lambda? durumundadır.

Sonraki Adım

Bir lambda’nın hatası argüman sayısıydı. Sonraki ders hata kavramının kendisine geçer: rescue bir yöntem değil, dilin kendi yapısıdır ve bu yapının hangi ifadelerde ne döndürdüğü ölçülecektir.

İ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