15 Şubat 2025 Cumartesi

Ruby Temelleri 2

https://ujk-ujk.blogspot.com/2025/02/ruby-temelleri-2.html
İçindekiler +

 Selam,

Yazının 2. bölümünde sınıflarla devam ediyoruz.



Sınıflar

Bu kısımla ilgili daha değişik bilgileri tercüme ettiğim Wikibooks/Ruby/Sınıflar sayfasında da bulabilirsiniz.

Bir sınıf tanımlamanın deyim yapısı.

class İsim
    # sınıf davranışını belirleyen bir kısım kod
end


Ruby'de sınıf isimleri, veri tipi olarak sabitlerdir, o yüzden büyük harfle başlamaları gerekir.

class Kedi # doğru!
end  

class köpek # yanlış, hata verir
end
# class.rb:4: class/module name must be CONSTANT (SyntaxError)



-- Üretici metod

Bir sınıfta sadece bir tane üretici metod olabilir ve adı initialize olur. Bu metod sınıftan yeni bir nesne üretildiğinde otomatik olarak çağrılır.

class Müşteri
    attr_reader :isim
    def initialize(isim)
        @isim = isim.capitalize
    end
end
ümit = Müşteri.new('Ümit')
p ümit.isim #=> 'Ümit'



-- Bir sınıf tanımlamak

class ifadesi ile sınıf tanımlaması yapılır.

class BirSınıf
end


Bir kere tanımlandıktan sonra sınıfın new metodu kullanılarak nesne oluşumları üretilir.

class BirSınıf
end

bir_nesne = BirSınıf.new
=> #<BirSınıf:0x00007fea2edd6858>



-- Erişim seviyeleri

Ruby'de 3 tane erişim seviyesi vardır: public, private ve protected.

private veya protected ifadelerinden sonra tanımlanan metodlar, o gruba aittir. Bunlardan önce tanımlanmış olan metodlar ise default olarak public'tir.


-- -- public metodlar

Public metodlar üretilen nesnenin davranışlarını belirlerler. Bu metodlar üretilen nesnenin dışında kalan kapsamdan çağrılabilir.

class Kedi
    def initialize(isim)
        @isim = isim
    end
 
    def konuş
        puts "Adım #{@isim} ve 2 yaşındayım"
    end
   
    #...
end
 
yeni_kedi = Kedi.new("garfield")
#=> #<Kedi:0x00007fed2f5363e8 @isim="garfield">
   
yeni_kedi.konuş
#=> Adım garfield ve 2 yaşındayım


Bu metodlar public, yeni Kedi nesnesi oluştururken ve o nesneyi konuştururken davranışını belirliyor (initialize ve konuş metodları).

public ifadesi gereksizdir, ama bazen private ya da protected bölümden çıkmak için kullanılabilir.

class BirSınıf
    def ilk_public_metod
    end
 
    private
 
    def private_metod
    end
 
    public
 
    def ikinci_public_metod
    end
end



-- -- private metodlar

Private metodlara nesne dışından erişilemez. Nesne içinde kullanılan metodlardır. Kedi örneğimize dönelim.

class Kedi
    def initialize(isim)
        @isim = isim
    end
 
    def konuş
        yaş = kedi_yaşı_hesapla # burada bir private metod çağırıyoruz
        puts "Adım #{@isim} ve #{yaş} yaşındayım"
    end
   
    private
    def kedi_yaşı_hesapla
        2 * 3 - 4
    end
end
 
yeni_kedi = Kedi.new("Bilbo")
yeni_kedi.konuş
#=> Adım Bilbo ve 2 yaşındayım
yeni_kedi.kedi_yaşı_hesapla
#=> private method `kedi_yaşı_hesapla' called for #<Kedi:0x...> (NoMethodError)

Gördüğünüz üzere yeni oluşturulan kedi nesnesi kedi_yaşı_hesapla private metoduna kendi içinde erişebiliyor. Metoddan dönen değeri yaş yerel değişkenine saklıyoruz ve kullanıyoruz. 

Ancak kedi_yaşı_hesapla metodunu yeni_kedi nesnesi dışında kullanmaya kalktığımızda bir NoMethodError hatası alıyoruz. 

Yine de private metoda ulaşmanın bir yolu var, send ile nesneye mesaj göndererek metodu çağırmak.

yeni_kedi.send :kedi_yaşı_hesapla
#=> 2

belki lazım olur.



-- -- protected metodlar

Protected metodlar ve private metodlar birbirine oldukça benzer, onlara da nesne dışından erişilemez. Ancak protected metodlara aynı sınıftan üretilen diğer nesnelerden ulaşılabilir. Bir örnekle açıklamaya çalışalım.

class Kedi
    def initialize(isim, yaş)
        @isim = isim
        @yaş = yaş
    end
 
    def konuş
        puts "Adım #{@isim} ve #{@yaş} yaşındayım"
    end

    # bu == metodu bize kedi nesnelerinin kendi_yaşı değerlerini karşılaştırır
    # eğer değerler eşitse kediler de eşittir
    def ==(diğer)
        self.kendi_yaşı == diğer.kendi_yaşı
    end
   
    protected
    def kendi_yaşı
        @yaş
    end
end
 
kedi1 = Kedi.new("ricky", 2)
#=> #<Kedi:0x00007f2330e2d628 @isim="ricky", @yaş=2>
kedi2 = Kedi.new("lucy", 4)
#=> #<Kedi:0x00007f2330c3bcc0 @isim="lucy", @yaş=4>
kedi3 = Kedi.new("felix", 2)
#=> #<Kedi:0x00007f2330c110b0 @isim="felix", @yaş=2>


Yeni bir Kedi nesnesi oluştururken parametrelere bir de yaş değeri ilave ettik. Kedilerimizin yaşlarını karşılaştırırken protected olan kendi_yaşı metodunu kullanıyoruz.

kedi1 == kedi2
#=> false
kedi1 == kedi3
#=> true


Aynı sınıftan başka bir nesnenin protected metoduna diğer.kendi_yaşı şeklinde erişebiliyoruz. Ama 

kedi1.kendi_yaşı
#=> protected method `kendi_yaşı' called for #<Kedi:0x...> (NoMethodError)

dışarıdan bu metoda ulaşılamaz. 



-- Oluşum değişkenlerine okuyucu ve yazıcı metodlarla erişmek

3 tane metodumuz vardır.

attr_reader : nesne dışından oluşum değişkenini okumak için

attr_writer : nesne dışından oluşum değişkenine yazmak için

attr_accessor : her iki metodun birleşimi


Örnek :

class Kedi
    attr_reader :yaş    # @yaş isimli değişkeni okuyabilir ama değiştiremezsiniz
    attr_writer :isim   # @isim isimli değişkeni değiştirebilir ama değeri okuyamazsınız
    attr_accessor :cins # @cins değerini hem okuyabilir hem değiştirebilirsiniz
 
    def initialize(isim, cins)
        @isim = isim
        @cins = cins
        @yaş = 2
    end
    def konuş
        puts "Adım #{@isim} ve ben #{@cins} cinsi kediyim"
    end
end
   
kedi1 = Kedi.new("Banjo", "birman")

# değer okumaları:
kedi1.yaş  #=> 2
kedi1.cins #=> "birman"
kedi1.isim #=> Error
 
# değer değiştirmeleri:
kedi1.yaş = 3 #=> Error
kedi1.cins = "sphynx"
kedi1.isim = "Bilbo"
 
kedi1.konuş #=> Adım Bilbo ve ben sphynx cinsi kediyim


Metodlarımıza parametre olarak semboller verdik, bunlar metodlar üretir. Mesela

class Kedi
    attr_accessor :cins
end
=> [:cins, :cins=]

Kodu irb oturumunda denediğimizde aslında bize cins ve cins= metodlarının üretildiğini söylüyor. Bu kod temelde şu kodla aynı işi yapar:

class Kedi
    def cins
        @cins
    end
    def cins= değer
        @cins = değer
    end
end

Deneyelim

>> kedi1 = Kedi.new
=> #<Kedi:0x00007f6fc80c6e00>
>> kedi1.cins
=> nil
>> kedi1.cins = "rrr"
=> "rrr"
>> kedi1.cins
=> "rrr"


Burada dikkat çeken şeylerden biri cins= metodunu cins = şeklinde boşlukla ayrılmış yazabiliyoruz. Bu Ruby'nin eşittir işareti ile biten metodlara uyguladığı bir ayrıcalık. Bu sayede diğer birçok dilde olduğu gibi get,set metodları gibi şeylerle veya property tanımlamaları ile uğraşmıyoruz. 

Dikkat edilmesi gereken bir nokta da mesela attr_accessor :cins dediğimizde oluşum değişkeni olan @cins değişkenine erişmekten bahsediyoruz, @ işaretinin yokluğu sizi yanıltmasın, Ruby'de oluşum değişkenleri @ ile başlamak zorundadır. 

Yine de oluşum değişken değerlerini işlerken standart dışı bir işlem yapacaksak, mesela bir değer doğrulaması yapacaksak, kendi metodlarımızı el ile tanımlamayı tercih edebiliriz.

class Kedi
    attr_reader :cins
    def cins= değer
        if değer.instance_of? String
            @cins = değer
        else
            @cins = "cins belirtilmemiş"
        end
    end
end
kedi1 = Kedi.new
#=> #<Kedi:0x00007f6fc80c6e00>
kedi1.cins
#=> nil
kedi1.cins = "rrr"
kedi1.cins
#=> "rrr"
kedi1.cins = 5
kedi1.cins
#=> "cins belirtilmemiş"

gibi. 



-- Sınıf metod tipleri

Sınıf metodu , oluşum metodu ve tekil metod olarak 3 değişik metod tanımlanabilir.


-- -- Oluşum metodları

Bu metodlar şimdiye kadar gördüklerimiz, ve sınıfın oluşum nesneleri kapsamında çağrılırlar. 

class BirSınıf
    def bir_metod
        puts "bir şey"
    end
end
 
foo = BirSınıf.new  # sınıfın oluşum nesnesi üretiliyor
foo.bir_metod       # => bir şey



-- -- Sınıf metodları

Bu metodlar sınıfın statik metodlarıdır ve oluşum nesnelerinden çağrılamazlar.

class BirSınıf
    def BirSınıf.merhaba isim
        puts "Merhaba, #{isim}!"
    end
end


Metod ismi başına sınıfın adını ve nokta koyarak bir sınıf metodu olduğunu belirtiyoruz. İstersek sınıf adı yerine self de koyabiliriz.

class BirSınıf
    def self.merhaba isim
        puts "Merhaba, #{isim}!"
    end
end


self kelimesi sınıf kapsamındayken sınıfı, oluşum metodu içindeyken de oluşum nesnesini ifade eder. 

Bu metodu yeni bir oluşum nesnesi oluşturmadan direk çağırırız.

BirSınıf.merhaba "Ümit Kayacık"
#=> Merhaba, Ümit Kayacık!


Ama sınıfın bir oluşum nesnesi üzerinden çağıramayız.

bir_nesne = BirSınıf.new
bir_nesne.merhaba
#=> undefined method `merhaba' for #<BirSınıf:0x...> (NoMethodError)



-- -- Tekil metodlar

Sınıfın sadece bir tane oluşum nesnesine özel tanımlanan metodlardır (singleton methods). Diğer nesnelerinde çalışmaz.

# boş bir sınıf tanımlayalım
class BirSınıf
end

# sınıftan 2 oluşum nesnesi
nesne1 = BirSınıf.new
nesne2 = BirSınıf.new

# nesnelerin birine tekil metod tanımı
def nesne1.birşey_yap
  puts "Ben bu nesneye aitim"
end

nesne1.birşey_yap
#=> Ben bu nesneye aitim
nesne2.birşey_yap
#=> undefined method `birşey_yap' for #<...> (NoMethodError)


Tekil ve sınıf metodlarına verilen bir isimlendirme de özsınıflar'dır. Basitçe düşünülürse Ruby bu metodları saklamak için bir anonim sınıf oluşturur ve böylece üretilen nesnelerin oluşum metodları ile karışmazlar. 

Bu metodları bir başka tanımlama şekli de class << yapısını kullanmaktır.

# bir sınıf metodu (yukarıdaki örnekle aynı)
class BirSınıf
    class << self # anonim sınıf
        def merhaba(isim)
            puts "Merhaba, #{isim}!"
        end
    end
end
 
BirSınıf.merhaba("Ümit") # => Merhaba, Ümit!
 

# tekil metod
 
class Nesneler
end
 
nesne1 = Nesneler.new
 
class << nesne1 # anonim sınıf
    def birşey_yap
       puts "Ben bu nesneye aitim"
    end
end
 
nesne1.birşey_yap
#=> Ben bu nesneye aitim



-- Dinamik sınıf üretimi

Sınıflar Class.new ifadesi ile dinamik olarak üretilir.

# yeni bir sınıfı dinamik üretmek
BirSınıf = Class.new

# BirSınıf sınıfının oluşumunu üretmek
bir_nesne = BirSınıf.new


Yukarıdaki örnekte yeni üretilen sınıf BirSınıf sabitine saklanıyor. Bu sınıf önceden gördüğümüz şekilde oluşum nesneleri üretmekte kullanılabilir.

Class.new metodu argümanında başka mevcut bir sınıf adını alarak yeni sınıfı ondan kalıtım yoluyla üretir (mirasçısı yapar). 

Dog = Class.new     # Ebeveyn sınıf (super class)

# başka bir sınıfın mirasçısı olarak yeni sınıf
Staffy = Class.new(Dog)

# Staffy tipinde bir nesne üret
lucky = Staffy.new
lucky.is_a?(Staffy) # true
lucky.is_a?(Dog)    # true


Class.new metodu argümanında bir blok da alabilir. Bu kod bloğu sınıf tanımının içinde yer almasını istediğiniz kodlardır.

Ördek =
  Class.new do
        def vrak
            'Vrrakk!!'
        end
  end

# Ördek tipinde bir nesne oluştur
ördek = Ördek.new
p ördek.vrak # 'Vrrakk!!'



-- new, allocate ve initialize

Ruby'de de diğer bir çok dil gibi sınıf oluşum nesneleri new sınıf metoduyla yapılır. Ama new bir deyim değildir, aslında o da bir sınıf metodudur , diğer sınıf metodlarından bir farkı yoktur. Tanımlaması kabaca şuna benzer. 

class MyClass
    def self.new(*args)
        obj = allocate
        obj.initialize(*args) # basitleştirilmiş örnek; initialize aslında private
        obj
    end
end


Esas sihiri sınıfın daha doğmamış nesnesini üreten allocate yapar. 

Dikkat edilmesi gereken nokta initialize metodu dönüş değeri kullanılmıyor, direk olarak obj nesnesine etki ediyor. 

Tüm sınıf metodlarının Class sınıfından aldığı new metodu yukarıdaki teknikle çalışıyor. Ama isterseniz değiştirebilir ya da farklı çalışan alternatif bir üretici metod tanımlayabilirsiniz. 

class MyClass
    def self.extraNew(*args)
        obj = allocate
        obj.foo
        obj.send :initialize, *args
        obj.bar
        obj
    end
    def foo
        p "üretim öncesi"
    end
    def initialize *args
        p "Üretim : #{args}"
    end
    def bar
        p "üretim sonu"
    end
end

nesne1 = MyClass.new(1,2,3,"boo")
#=> "Üretim : [1, 2, 3, \"boo\"]"
nesne2 = MyClass.extraNew(1,2,3,"boo")
#=> "üretim öncesi"
#=> "Üretim : [1, 2, 3, \"boo\"]"
#=> "üretim sonu"

gibi.



-- Sınıf ve oluşum değişkenleri

Sınıflarda verileri paylaşmak için kullanılan farklı değişken kapsamları vardır. Oluşum değişkenleri @ ile başlar, bunları görmüştük. Bunlar farklı metodlarda aynı değerlere ulaşmak için faydalıdır. 

class Kişi
    def initialize(isim, yaş)
        yaşım = yaş   # yerel değişken , nesne üretim sonrası yok olacak
        @isim = isim  # oluşum değişkeni, ancak nesne ile birlikte yok olur
    end
 
    def bir_metod
        puts "Benim adım #{@isim}." # @isim değerini başka metodda kullanabiliriz
    end
     
    def diğer_metod
        puts "Benim yaşım #{yaşım}." # bu çalışmayacak
    end
end
 
kişi1 = Kişi.new("Ümit", 59)
 
kişi1.bir_metod     #=> Benim adım Ümit.
kişi1.diğer_metod   #=> hata oluşturur


Sınıf değişkenleri @@ ile başlar ve o sınıftan üretilen tüm nesnelerde aynı değeri paylaşır. 

class Kişi
    @@toplam_kişiler = 0    # sınıf değişkeni sınıf tanımlandığında
                            # değeri sıfır
    def initialize(isim)
        @isim = isim  

        # her yeni nesne üretiminde sınıf değişkeni arttır
        @@toplam_kişiler += 1
    end
 
    def kaç_kişi_var
        puts "Üretilen kişi sayısı #{@@toplam_kişiler}"
    end
end
 
ümit = Kişi.new("Ümit")
ümit.kaç_kişi_var  #=> Üretilen kişi sayısı 1

zehra = Kişi.new "Zehra"
zehra.kaç_kişi_var #=> Üretilen kişi sayısı 2
ümit.kaç_kişi_var  #=> Üretilen kişi sayısı 2
# ümit yada zehra her ikisine de sorulabilir


Global değişkenler $ ile başlar ve programın her yerinden erişilebilir, bu yüzden dikkatli kullanınız. 

$toplam_hayvan = 0

class Kedi
  def initialize
    $toplam_hayvan += 1
  end
end

class Köpek
  def initialize
    $toplam_hayvan += 1
  end
end

bob = Kedi.new()
puts $toplam_hayvan #=> 1
fred = Köpek.new()
puts $toplam_hayvan #=> 2






Hash veri tipi

Hash sözlük benzeri bir listedir, anahtar kelimeler (key), ve karşılığı değerlerden (value) oluşur. İlişkisel array (associative arrays) dendiği de olur. Yapıları array'e benzer, ancak array'lerde elemanların index değerleri vardır (sıra numaraları) ve onlarla erişilir. Hash index yerine herhangi bir nesne kullanmanıza olanak tanır, bunlara nahtar kelime (key) denir. Yeni bir girdiyi anahtar kullanarak tanımlar ve sonra yine bu anahtar ile erişirsiniz. 

Örnekler :

{ isim: "Ümit", soyad: "Kayacık" }
{ :isim => "Ümit", :soyad => "Kayacık" }
{ "Ön Adı" => "Ümit", "Soy Adı" => "Kayacık" }
{ ilk_key => ilk_değer, ikinci_key => ikinci_değer } 


Ruby'de hash'ler bir hash tablosu kullanarak anahtarlar değerlere bağlanır. 

Anahtar olarak herhengi bir nesne kullanılabilir ama Ruby'de genel eğilim sembolleri kullanarak hafıza etkinliğini arttırmaktır (string'ler bile bir nesne olarak yer işgal ederken sembollerin sadece değerleri vardır).

{ key1: "foo", key2: "baz"  }

gibi.



-- Bir Hash tanımlamak

Ruby'de bir hash hash table uygulaması bir nesnedir, ve anahtarları değerlere eşleştirir. Ruby hash değerleri { } özel yapısıyla tanımlama imkanı sunar. 

hash1 = {}  # boş bir hash
yaşlar = { 'Ümit' => 59, 'Serpil' => 63, 'Sezgin' => 38 }


Bir hash standart new metodu ile de tanımlanabilir.

hash1 = Hash.new    # boş bir hash
hash1 = {}          # boş bir hash


Hash içinde her türde değer olabilir, hatta karmaşık tipler olan array, nesne ve diğer hash'ler gibi eleman değerleri olabilir. 

mapping = { 'Mark' => 15, 'Jimmy' => [3,4], 'Nika' => {'a' => 3, 'b' => 5} }
mapping['Mark']   # => 15
mapping['Jimmy']  # => [3, 4]
mapping['Nika']   # => {"a"=>3, "b"=>5}


Anahtarlar da her veri tipinde olabilir.

mapping = { 'Mark' => 15, 5 => 10, [1, 2] => 9 }
mapping['Mark']  # => 15
mapping[[1, 2]]  # => 9


Hash anahtar kelimesi olarak semboller çok yaygın kullanılır. Ruby 1.9 sonrasında bunları kullanmanın daha kestirme bir yoluna geçildi.

# tüm Ruby versiyonlarında geçerli
grades = { :Mark => 15, :Jimmy => 10, :Jack => 10 }
# Ruby versiyon 1.9+ geçerli
grades = { Mark: 15, Jimmy: 10, Jack: 10 }


Aşağıdaki hash ise (tüm versiyonlarda geçerlidir) farklı, çünkü anahtar değerleri string. Burada kestirme kullanamazsınız, sadece sembol için geçerli kural.

grades = { "Mark" => 15, "Jimmy" => 10, "Jack" => 10 }


Bu ikisinden karışım yapılabilir.

mapping = { :length => 45, width: 10 }


Versiyon 2.2 sonrasında çok kelimeden oluşan sembol değerleri için şu notasyon da geçerli oldu. 

grades = { "Jimmy Choo": 10, :"Jack Sparrow": 10 }
=> { :"Jimmy Choo" => 10, :"Jack Sparrow" => 10}


Bu hash veri tipini Ruby on Rails programcıları çok kullandığı için sürekli basitleştirmeye çalışıyorlar.



-- Default hash değerleri vermek

Default olarak eğer olmayan bir anahtar okunmak istenirse nil değer döner. İsterseniz olmayan bir hash anahtarına ulaşılmaya çalışıldığında dönecek değeri başka bir default değere ayarlayabilirsiniz. Her ne kadar adına default değer dense de isterseniz bir hesaplama sonucunu da (mesela anahtar karakter sayısı) default değer olarak ayarlayabilirsiniz. 

Default değer hash üretim metodunda argüman olarak verilir.

>> h = Hash.new(0)
=> {}
>> h[:hi] = 1
=> 1
>> h[:hi]
=> 1
>> h[:bye]
=> 0 # nil yerine default değer dönülür


Halihazırda tanımlanmış olan bir hash için de default değer verilebilir.

>> hash1 = { insan: 2, hayvan: 1 }
>> hash1[:bitki]
=> nil
>> hash1.default = 0
>> hash1[:bitki]
=> 0


Bu satır başında >> olanlar irb denemeleri oluyor, umarım yanlış anlaşılmıyordur.

Default değer olmayan her anahtara erişilmeye çalışıldığında kopyalanmaz, bu da bize default değerin aslında referans olarak alındığını gösterir. 

>> authors = Hash.new([]) #default []
=> {}
>> authors.default
=> []
>> authors[:homer] << 'The Odyssey' # olmayan değere ekleme
=> ["The Odyssey"]
>> authors.default
=> ["The Odyssey"]
>> authors[:plato]
=> ["The Odyssey"]
>> authors
=> {}  # hala boş
>> authors[:plato] << :foo  # olmayan değere ekleme
>> authors.default
=> ["The Odyssey", :foo]
>> authors[:homer]
=> ["The Odyssey", :foo]
>> authors
=> {}  # hala boş


Burada olmayan değeri bir hesap sonucu belirleyerek sorunu çözebiliriz.

>> authors = Hash.new { |hash, key| hash[key] = [] }
=> {}
>> # olmayan hash elemanı çağrılınca hesaplanan sonuç eklenir
>> authors[:homer] << 'The Odyssey' # authors[:homer]'a ekleme
=> ["The Odyssey"]
>> authors[:plato]
=> []
>> authors
=> {:homer=>["The Odyssey"], :plato=>[]} # anahtarlar var artık


Bu default değer kod bloğunu başka verilere göre bir değer oluşturmak amacıyla da kullanabiliriz. 

>> chars = Hash.new { |hash,key| key.length }
=> {}
>> chars[:test]
=> 4
>> chars[:babil]
=> 5
>> chars
=> {}

sadece default değer hesaba göre belirleniyor, önceki koddaki gibi hash içine eleman eklemek yok (hash[key] = [] gibi bir kod yok). 

Çok karmaşık hash elemanları da üretebilirsiniz.

>> page_views = Hash.new { |hash, key| hash[key] = { count: 0, url: key } }
=> {}
>> page_views["http://example.com"][:count] += 1
>> # anahtar yoksa hem eklenip hem count değeri bir arttırılıyor
>> page_views
=> {"http://example.com"=>{:count=>1, :url=>"http://example.com"}}
>> page_views["http://example.com"][:count] += 1
=> # anahtar var olduğu için olan count değeri arttırılıyor
>> page_views
=> {"http://example.com"=>{:count=>2, :url=>"http://example.com"}}


Default değer için kod bloğu alındığı gibi Proc nesnesi alabilmek amacıyla default_proc nesne metodu kullanılabilir.

>> authors = {}
=> {}
>> authors.default_proc = proc { [] }
=> #<Proc:0x00007fcf0e299d68 (irb):43>
>> authors[:homer] += ['The Odyssey']
=> ["The Odyssey"] # default üzerine ekleme
=> # aslında olan değere ekleme ama değer olmayınca default
=> # üzerine eklenip yeni değer oluşturulur
>> authors[:plato]
=> []  # olmayayn değer döner
>> authors
=> {:homer=>["The Odyssey"]}
>> authors[:homer] += ['The Odyssey2'] # olana ekleme
>> authors
=> {:homer=>["The Odyssey", "The Odyssey2"]}



-- Hash eleman değerlerine erişmek

Elemanların değerlerine erişmek ve değiştirmek için daha önceki örneklerde de gördüğümüz gibi [ ] ve [ ]= metodları kullanılır.

hash1 = { uzunluk: 4, genişlik: 5 }

hash1[:uzunluk] #=> => 4

hash1[:yükseklik] = 9
hash1 #=> {:uzunluk=>4, :genişlik=>5, :yükseklik=>9}


Default olarak olmayan bir hash anahtarına erişmeye çalışırsanız nil değer döner, böylece hata oluşmadan güvenli bir şekilde olmayan anahtar sorgulanabilir. 

>> hash1 = {}
=> {}
>> hash1[:yaşı]
=> nil


Hash her nesneyi anahtar olarak kabul ettiği için string bir değeri de kabul eder. String yerine sembol gibi erişmeye kalkarsanız nil değer döner.

>> hash1 = { "isim" => "user" }
=> {"isim"=>"user"}
>> hash1[:isim]
=> nil
>> hash1["isim"]
=> "user"


Eğer ille de anahtar değer bulunsun , yoksa hata oluşsun isterseniz, fetch metodu verilen anahtar yoksa bir hata oluşturur. 

>> hash1 = {}
=> {}
>> hash1.fetch(:yaşı)
(irb):63:in `fetch': key not found: :yaşı (KeyError)


fetch metodu ikinci argümanında eğer anahtar bulunamazsa dönülecek default değeri alabilir.

>> hash1 =  {}
=> {}
>> hash1.fetch(:yaşı, 59)
=> 59
>> hash1
=> {}


fetch metodu bir blok alarak da bulunamayan anahtarlarda dönecek değeri hesap yoluyla belirleyebilir. 

>> hash1 = {}
=> {}

>> hash1.fetch(:yaşı) { 59 }
=> 59

?> hash1.fetch(:yaşı) do |k|
?>   puts "'#{k}' anahtarı bulunamadı"
>> end
#=> 'yaşı' anahtarı bulunamadı


Hash'lerde [ ]= metodu yerine store metodunu da kullanabilirsiniz. 

>> hash1 = {}
=> {}
>> hash1.store(:yaş, 59)
=> 59
>> hash1
=> {:yaş=>59}


values metodu ile tüm değerleri alabilirsiniz.

>> hash1 = { uzunluk: 4, genişlik: 5 }
=> {:uzunluk=>4, :genişlik=>5}
>> hash1.values
=> [4, 5]


Not: bu sadece Ruby 2.3+'da geçerlidir. İç içe hash'lerde dig metodu kullanışlıdır. 

>> h = { foo: {bar: {baz: 1}}}
=> {:foo=>{:bar=>{:baz=>1}}}
>> h.dig(:foo, :bar, :baz)
=> 1 # :foo içindeki, :bar içindeki, :baz değeri
>> h.dig(:foo, :zot, :xyz)
=> nil # karşılığı yok
>> g = { foo: [10, 11, 12] }
=> {:foo=>[10, 11, 12]}
>> g.dig(:foo, 1)
=> 11 # :foo değerinin 1 indexli elemanı


-- Otomatik olarak iç içe hash oluşturmak

Önce de görmüştük olmayan anahtarlar için hash default değeri nil'dir.

>> a = {}
=> {}
>> a[ :b ]
=> nil


Default değeri üretici metoda argüman olarak girebiliyorduk.

>> b = Hash.new 'puppy'
=> {}
>>  b[ :b ]
=> "puppy"


Default değerin bir hesaplama sonucu olması için bir kod bloğuyla da verebiliyorduk. Lınux shell komutu mkdir -p, veya Perl'ün autovivification davranışı gibi, blok yardımıyla iç içe hash oluşturabiliriz. 

# h tanımladığınız has ve k de anahtardır.
#
hash = Hash.new { |h, k| h[k] = Hash.new &h.default_proc }
hash[ :a ][ :b ][ :c ] = 3

p hash # => { a: { b: { c: 3 } } }


:a anahtarı yok - default_proc çalışacak, default_proc ne? zaten verilmiş olan blok - :a nın değeri bir yeni hash olacak. :a'nın içinde :b anahtarı yok ona da aynı işlem yapılıp değeri yeni bir hash olacak. En son :b^nin içindeki :c'ni değeri 3 olacak. 



-- Hash elemanlarında iterasyon

Bir Hash içinde birçok yararlı metodlar olan Enumerable modülünü içerir. Bunlardan bazıları: Enumerable#each, Enumerable#each_pair, Enumerable#each_key, ve Enumerable#each_value.

each ve each_pair hash içindeki her anahtar-değer çifti için iterasyon yapar.

h = { "adı" => "Ümit", "soy adı" => "Kayacık" }
h.each do |anahtar, değer|
    puts "#{anahtar} = #{değer}"
end

#=> adı = Ümit
#=> soy adı = Kayacık


each_key sadece anahtarlar üzerinden iterasyon yapar.

h = { "adı" => "Ümit", "soy adı" => "Kayacık" }
h.each_key do |anahtar|
    puts anahtar
end

#=> adı
#=> soy adı


each_value sadece değerler üzerinden iterasyon yapar.

h = { "adı" => "Ümit", "soy adı" => "Kayacık" }
h.each_value do |değer|
    puts değer
end

#=> Ümit
#=> Kayacık


each_with_index metodu tüm elemanlar üzerinden iterasyon yaparken size bir de index numarası verir.

h = { "adı" => "Ümit", "soy adı" => "Kayacık" }
h.each_with_index do |(anahtar, değer), index|
    puts "Index: #{index} | anahtar: #{anahtar} | değer: #{değer}"
end

#=> Index: 0 | anahtar: adı | değer: Ümit
#=> Index: 1 | anahtar: soy adı | değer: Kayacık


Burada (anahtar, değer) ifadesi parantez içindedir, sebebi each_with_index metodu elemanı ve index değerini döner, ama bir hash nesnesine uygulanınca eleman olarak bir anahtar-değer çifti dönecektir.  Şöyle de yazılabilir:

h.each_with_index do |eleman, index|
    puts "Index: #{index} | anahtar: #{eleman[0]} | değer: #{eleman[1]}"
end



-- Array - Hash dönüşümleri

Hash array'e , array de hash'e dönüştürülebilir. Bir hash değeri anahtar-değer çiftleri içeren alt array'ler şeklinde dönüştürmek için to_a metodu kullanılır.

>> { :a => 1, :b => 2 }.to_a
=> [[:a, 1], [:b, 2]]


Tersi yönde bir hash de bu düzende yazılmış bir array'den to_h metodu ile dönüşütürülebillir.

>> [[:x, 3], [:y, 4]].to_h
=> {:x=>3, :y=>4}


Benzer bir şekilde yeni hash tanımlarken Hash[ ] ifadesi kullanarak sıralı anahar-değer dizisi ile tanımlanabilir.

>> Hash[:a, 1, :b, 2]
=> {:a=>1, :b=>2}

# ya da
>> a = [[:x, 3], [:y, 4]]
>> Hash[a]
=> {:x=>3, :y=>4}

# ya da
>> a = [:x, 3, :y, 4]
>> Hash[*a]
=> {:x=>3, :y=>4}


Hash değerler sıralı anahtar-değer elemanları içeren array'e flatten metodu ile dönüştürülür.

>> {:a=>1, :b=>2}.flatten
=> [:a, 1, :b, 2]


İç içe array'den hash'e dönüştürme yapısı Enumerable modülünün metodları yardımıyla daha etkili kullanılabilir.

>> Hash[('a'..'d').collect{ |x| [x, x.upcase] }]
=> {"a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D"}
# çiftler alt array'lerde toplanıyor

>> insanlar = ['Ümit', 'Hasan', 'Deniz']
=> ["Ümit", "Hasan", "Deniz"]
>> boylar = [1.74, 1.80, 1.68]
=> [1.74, 1.8, 1.68]
>> Hash[insanlar.zip(boylar)]
=> {"Ümit"=>1.74, "Hasan"=>1.8, "Deniz"=>1.68}
# .zip array elemanlarını birleştirerek iç içe array yapar
>> [1, 2, 3].zip(["a", "b", "c"])
=> [[1, "a"], [2, "b"], [3, "c"]]



-- Hash elemanlarını filtrelemek

select metodu arkasından verilen kod bloğunun true değer döndüğü elemanları içeren yeni bir hash üretir.

>> {:a => 1, :b => 2, :c => 3 }.select { |k, v| k != :a && v.odd? }
=> {:c=>3} # anahtarı :a olmayan ve değeri tek sayı olanlar


Eğer filtreleme bloğu içerisinde anahtar ya da değerden birini kullanmayacaksanız, kod temizliği açısından yerine ( _ ) koyabilirsiniz.

>> {:a => 1, :b => 2, :c => 3 }.select { |_, v| v.even? }
=> {:b=>2}

>> {:a => 1, :b => 2, :c => 3 }.select { |k, _| k == :c }
=> {:c=>3}


reject metodu ise bloğun false değer döndüğü çiftlerden yeni bir hash oluşturur.

>> {:a => 1, :b => 2, :c => 3 }.reject { |_, v| v.even? }
=> {:a=>1, :c=>3} # değeri çift sayı olmayanlar
>> {:a => 1, :b => 2, :c => 3 }.reject { |k, _| k == :b }
=> {:a=>1, :c=>3} # anahtarı :b olmayanlar

Böylece aynı satırı bir select, bir reject ile kullanarak hash değeri filtreye uyanlar ve uymayanlar olarak ikiye parçalayabilirsiniz. 



-- Tüm anahtar ya da değerleri bulmak

>> {foo: 'bar', biz: 'baz'}.keys
=> [:foo, :biz]
>> {foo: 'bar', biz: 'baz'}.values
=> ["bar", "baz"]
>> {foo: 'bar', biz: 'baz'}.to_a
=> [[:foo, "bar"], [:biz, "baz"]]
>> {foo: 'bar', biz: 'baz'}.each
=> #<Enumerator: ...>



-- hash metodunun üstüne yazmak

Ruby hash değerlerde elemanlara atama yaparken aynı nesnelere sahip anahtarlara aynı değerleri vermek için hash ve eql? metodlarını kullanır. Bu metodların üstüne yazarak istediğimiz davranışı belirleyebiliriz.

Bir örnek verelim

class A
    def initialize(hash_value)
        @hash_value = hash_value
    end
end
 
class B < A
end
 
a = A.new(1)
b = B.new(1)
 
h = {}
h[a] = 1
h[b] = 2

p h
#=> {#<A:0x... @hash_value=1>=>1, #<B:0x... @hash_value=1>=>2}
p a.hash
#=> 4265902018679903186
p b.hash
#=> -2136923461570796363


Fakat diyelim biz her iki nesneye de aynı @hash_value değerini girdiğimiz için hash içinde aynı elemanlara erişmesini istiyoruz, veride ikileme olmasın istiyoruz. Bu durumda sınıf tanımında hash metodu ve Ruby'nin karşılaştırırken kullandığı eql? üzerine yazarız.

class A
    def initialize(hash_value)
        @hash_value = hash_value
    end
    def hash
        @hash_value # dışardan girilen değeri döner
    end
    def eql?(b)
        self.hash == b.hash
    end
end
 
class B < A
end
 
a = A.new(1)
b = B.new(1)
 
h = {}
h[a] = 1
h[b] = 2

p h
#=> {#<A:0x00007f4bb0d95fc8 @hash_value=1>=>2}
p a.hash
#=> 1
p b.hash
#=> 1


a nesnesi daha önce tanımlandığı için onu aldı ve b anahtarı kullanarak yazılan değeri de aynı kutuya yazdı (çünkü ikisini eşit görüyor).



-- Hash elemanlarında anahtar ve değerleri değiştirmek

Yeni bir hash tanımlarken anahtar ve değerleri vermesini gördük, sonradan anahtarlara değer atamasını gördük, sonradan nahtarı değiştirme ya da silme işi için de inject  (diğer adıyla reduce) metodu kullanılır. Örneğin hash elemanlarının anahtarlarını string'e dönüştürmek ve değerlerini büyük harf yapmak için bir kod yazalım.

meyve = { isim: 'elma', renk: 'yeşil', şekil: 'yuvarlak' }
# => {:isim=>"elma", :renk=>"yeşil", :şekil=>"yuvarlak"}

yeni_meyve = meyve.inject({}) { |memo, (k,v)| memo[k.to_s] = v.upcase; memo }

# => yeni_meyve içeriği {"isim"=>"ELMA", "renk"=>"YEŞIL", "şekil"=>"YUVARLAK"}


Hash bir enumerable'dır ve özünde anahtar-değer çiftlerinden oluşur. Bu yüzden each, map ve inject gibi metodları vardır. 

Hash içindeki her anahtar-değer veri çifti için inject arkasından verilen bloktaki kod çalıştırılır. İlk turda memo değişkeni inject metodunun argümanı nesneyi içerir, burada boş bir hash nesnesi. Bloğa geçen ikici değer ise kolleksiyondaki eleman, eğer bu bir array olsa ya da bir range, orada o turda işlenecek eleman değeri olacaktı, bu bir hash olduğu için bir anahtar-değer çifti gelecektir, onu da parantez içinde k (key) ve v (value) şeklinde ikiye ayırdık. 

meyve hash verisindeki ilk eleman ( :isim=>"elma" ) çifti, bu eleman kod bloğu içinde işlenince memo akümülatör değişkeninin ilk elemanı oluşuyor ( "isim"=>"ELMA" ). Kalınan yerden eklemeye sonraki iterasyonlarda devam edilsin diye memo değişkenini bloktan dönen değer yapıyoruz, çünkü ilk turdan sonraki turlarda kod bloğundan dönen değer sonraki turda memo akümületör değeri olarak kullanılacak. Tabi bunu inject metodu yapacak , zaten bizden argümanla aynı tipte değer bekleyecektir, kendisine bloktan bir hash dönmek zorundayız. Dönmesek ne olur?

>> yeni_meyve = meyve.inject({}) { |memo, (k,v)| memo[k.to_s] = v.upcase }
(irb):7:in `[]=': string not matched (IndexError)

sınuçta koddan dönen değer string (mesela "ELMA" gibi), ve bir sonraki turda bu dönen değer hash olmadığı için hata oluşturacak. Neler olduğunu görmek için kod ekleyelim.

>> yeni_meyve = meyve.inject({}) { |memo, (k,v)| p memo; memo[k.to_s] = v.upcase }
{}
"ELMA"
(irb):9:in `[]=': string not matched (IndexError)

Görüjdüğü üzere ilk turda memo={} - ikinci turda ise ilk turun dönen değeri olan "ELMA". Tabii memo="ELMA" olunca ikinci turda oluşturulacak memo["renk"] ifadesi bir hata oluşturuyor (İşin kötüsü .string değer "renk" içerse hata vermezdi).


Her seferinde son ulaşılan değeri kod bloğundan dönmek yerine each_with_object metodu da kullanılabilir.

yeni_meyve = meyve.each_with_object({}) { |(k,v), memo| memo[k.to_s] = v.upcase }

Bu da aynı işi yapar, ama burada önce eleman sonra nesne veriliyor argümanda, dikkat ediniz. 

Ya da map metodu kullanırız.

>> yeni_meyve = Hash[meyve.map{ |k,v| [k.to_s, v.upcase] }]
=> {"isim"=>"ELMA", "renk"=>"YEŞIL", "şekil"=>"YUVARLAK"}



-- Hash'ler arası işlemler

-- -- Hash'lerin kesişimi bulunması

Hash'lerin anahtar-değer çiftlerinde eşit olanları kesişimi oluşturur. 

>> hash1 = { :a => 1, :b => 2 }
>> hash2 = { :b => 2, :c => 3 }
>> hash1.select { |k, v| (hash2.include?(k) && hash2[k] == v) }
=> {:b=>2}



-- -- Hash'lerin bileşimi bulunması

Hash içinde anahtar değerleri tek olur, birleştirilen hash'lerde merge metodunun uygulandığı hash nesnesindeki anahtarın üstüne yazılır.

>> hash1 = { :a => 1, :b => 2 }
>> hash2 = { :b => 4, :c => 3 }

>> hash1.merge(hash2)
=> {:a=>1, :b=>4, :c=>3}
>> hash2.merge(hash1)
=> {:b=>2, :c=>3, :a=>1}



Blok'lar Proc'lar ve Lambda'lar

-- Blok deyimleri

Proc.new(block)

lambda { |args| code }

->(arg1, arg2) { code }

object.to_proc

{ |single_arg| code }

do |arg, (key, value)| code end



-- Bloklarda dikkat edilmesi gerekenler

Birçok metodu zincirleme bağlarken operatör önceliklerine dikkat ediniz.

str = "abcdefg"
puts str.gsub(/./) do |match|
  rand(2).zero? ? match.upcase : match.downcase
end
=> #<Enumerator:0x00007f2d50c367f0>


Bu kodun abCDeFg gibi bir çıktı vermesini beklerken bir enumerator nesnesi verdi. Sebebi do...end ifadesinin önceliğinin metodlardan düşük olması , puts metodu sadece puts str.gsub(/./) olarak çalıştı, do bloğunun arkadan geldiğini gsub algılayamadı. 

Bunun üstesinden gelmek için ya bloğu { } içinde yazarız,

str = "abcdefg"
puts str.gsub(/./) { |match|
  rand(2).zero? ? match.upcase : match.downcase
}


ya da gsub metodunu komple parantez içine alırız.

str = "abcdefg"
puts (str.gsub(/./) do |match|
  rand(2).zero? ? match.upcase : match.downcase
end
)


gibi. 



-- Lambda'lar

Lambda'lar bir kod bloğunu parametreleri ile tanımlamaya yarayan nesnelerdir, ve bir değişkene konabilirler.

# ok yapısı ile lambda tanımlama
merhaba = -> { 'Merhaba Dünya!' }
#=> #<Proc:0x00007f00169ccff8 (irb):1 (lambda)>
merhaba[]
#=> "Merhaba Dünya!"

# ok yapısında 1 argüman alan lambda
merhaba = ->(isim) { "Merhaba #{isim}!" }
merhaba['Ümit']
#=> "Merhaba Ümit!"

# do...end bloğu ile lambda
birşey = lambda do |p1, p2, p3|
  puts "p1 = #{p1}"
  puts "p2 = #{p2}"
  puts "p3 = #{p3}"
end

birşey.call(1, 2, 3)
#=> p1 = 1
#=> p2 = 2
#=> p3 = 3
birşey[1,2,3]
# yukardaki ile aynı

birşey.call(1, 2)
# ArgumentError: wrong number of arguments (2 for 3)

birşey[1, 2, 3, 4]
# ArgumentError: wrong number of arguments (4 for 3)


Lambda ve Proc birbirine benziyor ama farkları var:

  • Lambda argümanları zorunludur, bir lambda'ya yanlış sayıda argüman girmek ArgumentError hatası verir. Ama default değerler vs kullanılabilir.
  • Bir lambda'dan dönüşte sadece lambda'dan dönülür ama bir proc'dan dönüşte onu kapsayan bloktan da çıkılır.

def proc_dnm
  x = Proc.new {
    return # proc_dnm'den dönüş
  }
  x.call
  puts "x.call sonrası" # bu satıra asla erişilmez
end

def lambda_dnm
  y = -> {
    return # y'den dönüş
  }
  y.call
  puts "y.call sonrası" # bu satıra ulaşılır
end

proc_dnm # Çıktı olmaz
lambda_dnm # "y.call sonrası" yazar



-- curry metodu ve parça kullanma

Teknik olarak Ruby'de fonksiyonlar yok , ama metodlar aynı diğer dillerdeki fonksiyonlar gibi davranır. 
def double(n)
  n * 2
end


Bu metod/fonksiyon parametre olarak n değerini alır, ikiye katlar ve değeri döner. Haydi daha gelişmiş bir metod yazalım.

def triple(n)
  lambda {3 * n}
end


triple metodu bize değer dönmek yerine bir lambda kod bloğu dönüyor. IRB oturumunda deneyelim.

>> def triple(n)
>>   lambda {3 * n}
>> end
=> :triple
>> def double(n)
>>   n * 2
>> end
=> :double
>> double(2)
=> 4
>> triple(2)
=> #<Proc:0x00007fea53bf7a58 (irb):2 (lambda)>


Gerçekten 3'e katlanmış bir sayı istiyorsak metoddan dönen lambda'yı çalıştırmalıyız.

>> triple_two = triple(2)
>> triple_two.call
=> 6

ya da kısacası

>> triple(2).call
=> 6



-- -- curry metodu ile parçalı çalışmak

Yukarıda anlattıklarımız basit işler yaparken pek yararlı görünmüyor. Ama benzer işi yapan birçok metod tanımlarken çok işimize yarar. Diyelim verdiğimiz sayıya bir, iki ve üç ekleyen metodlar tanımlayacağız.

def bir_ekle(n)
  n + 1
end

def iki_ekle(n)
  n + 2
end

def üç_ekle(n)
  n + 3
end

p bir_ekle 2 #=> 3
p iki_ekle 2 #=> 4
p üç_ekle 2  #=> 5


Bir sürü benzer metod tanımlaması yerine, curry metodu kullanabiliriz.

ekle = -> (a, b) { a + b }
bir_ekle = ekle.curry.(1)
iki_ekle = ekle.curry.(2)
üç_ekle = ekle.curry.(3)

p bir_ekle.call 2 #=> 3
p iki_ekle.call 2 #=> 4
p üç_ekle.call 2  #=> 5


Lambda hesaplamasına göre ekle lambda ifadesi (λa.(λb.(a+b))) olarak gösterilir. curry ile ekle lambda'sına parçasal erişim tanımlıyoruz. Bu durumda ekle.curry.(1) karşılığı (λa.(λb.(a+b)))(1) olur, yani (λb.(1+b)) olur. Böylece ekle bloğuna gidecek ilk argümanı vermiş ikincisini kullanımına bırakmış oluruz. 



-- -- Başka curry örnekleri

Diyelim çok büyük genel bir formülümüz var ve bazı değerleri sabit girerek azaltılmış özel formüller üretebileceğimizi düşünüyoruz.

f(x, y, z) = sin(x*y)*sin(y*z)*sin(z*x)


Bu formül üç boyutta çalışıyor. Fakat diyelim biz bu formülün sadece x=PI/2 olduğu y-z iki boyutlu düzlemindeki formülünü bulmak istiyoruz. Öncelikle Ruby diline uygun formülümüzü oluşturalım.

f = ->(x, y, z) {Math.sin(x*y) * Math.sin(y*z) * Math.sin(z*x)}


Şimdi y-z düzlemindeki özel formülümüzü üretelim.

f_yz = f.curry.(Math::PI/2)


Sonra da yeni formülü deneyelim.

f_yz.call(Math::PI, Math::PI/2)
#=> 0.5938908450203798


İlk parametre olan x'i dışarda bırakmayı gördük. Peki ikinci parametreyi dışarda bırakıp f_xz özel formülüne ulaşmak istersek? Evet, orası biraz daha karışık.

f_xz = -> (x,z) {f.curry.(x, Math::PI/2, z)}


Benzer şekilde z değerinin sabit olduğu formüle de erişebiliriz.

f_xy = -> (x,y) {f.curry.(x, y, Math::PI/2)}

gibi.


-- Metodlara nesneleri blok argüman olarak vermek

Bir metoda argüman girerken önüne & ekleyince , argüman eğer bir metodsa kod bloğu, bir nesneyse onun to_proc metodunun dönen değeri (bir Proc nesnesi) argümana verilir. 

class Selamlayıcı
  def to_proc
    Proc.new do |item|
      puts "Merhaba, #{item}"
    end
  end
end

selam = Selamlayıcı.new

%w(Dünya Hayat).each(&selam)
# Merhaba, Dünya
# Merhaba, Hayat


Bu Ruby'de genel bir şablondur ve birçok standart sınıfta bulunur. 

Örneğin Symbol sınıfının to_proc uyarlaması argümana kendisini göndermesi şeklindedir. 

class Symbol
  def to_proc
    Proc.new do |receiver|
      receiver.send self
    end
  end
end

basitleştirilmiş yapısı buna benzer. Yani bir nesneye & ile bir sembol argüman verildiğinde o nesnede sembol ile belirtilen metod çağrılır. Bildiğimiz üzere send metodu o nesnede metod çağırır. 

>> "a".upcase
=> "A"
>> "a".send("upcase")
=> "A"

gibi.

Bu &:sembol yapısı Enumerable nesnelerde çok faydalıdır.

>> harf_sayıları = %w(bir kısım kelime).map(&:length)
=> [3, 5, 6]



-- Proc nesnesine çevirmek

to_proc'a yanıt veren nesneler (yani to_proc metodu tanımlı olan nesneler) & operatörüyle proc'lara dönüştürülebilir (böylece argümana blok olarak verilebilirler).

Symbol sınıfında to_proc tanımlıdır, bu sayede parametresinde verildiği nesne üzerinde karşılık gelen metodu çalıştırır.

>> p [ 'tavşan', 'çim' ].map( &:upcase )
=> ["TAVŞAN", "ÇIM"]


Bu aslında 

p [ 'tavşan', 'çim' ].map {|x| x.upcase}

kodunun eşleniği. Sembol değer map metodundan gönderilen x nesnesini alıp onun upcase metodunu otomatik olarak çağırıyor. 

Metod nesnelerinde de (Ruby'de herşey bir nesnedir) to_proc tanımlıdır. 

>> çıktı = method( :p )
>> ['tavşan', 'çim' ].map( &çıktı )
=> "tavşan"
=> "çim"



-- Kod blokları

Kod bloklarının tek satır için { } arasında , çok satır için do...end bloğu içinde gösterilmesi tavsiye edilir. 

5.times { puts "Merhaba Dünya" }  
# tek satır kodlar için tercih edilir

5.times do
  print "Merhaba "
  puts "Dünya"
end # çok satır kodlar için tercih edilir

5.times {
    print "Merhaba "
    puts "Dünya"
}
# çalışır ama tavsiye edilmez


Not: Süslü parantezlerin do...end bloklarından öncelikli çalıştırıldığını unutmayınız.



-- -- yield ile blok kodunu çalıştırmak

Bloglar argümanında verildikleri metodların içinde yield ifadesi ile kullanılırlar. 

def blok_çağırıcı
  puts "bir kısım kod"
  yield
  puts "başka bir kod"
end
blok_çağırıcı { puts "Bu blok benim" } # blok metoda bir argüman gibi veriliyor
# bir kısım kod
# Bu blok benim
# başka bir kod


Dikkat edilmesi gereken konu, argümanda blok verilmeden yield çağrılırsa hata verir.

blok_çağırıcı
# bir kısım kod
# no block given (yield) (LocalJumpError)


Eğer blok verilmese de metodumuzun hata vermeden devam etmesini istersek block_given? metodunu kullanabiliriz.

def blok_çağırıcı
  puts "bir kısım kod"
  if block_given?
    yield
  else
    puts "default"
  end
  puts "başka bir kod"
end
blok_çağırıcı
# bir kısım kod
# default
# başka bir kod


yield ifadesi bağlı bloğa argüman da gönderebilir. 

def yield_n(a)
  z = yield a if block_given?
  z || a
end
p yield_n(12) {|x| x + 7 }
#=> 19
p yield_n(4)
#=> 4


Bu basit bir örnek gibi görünse de yield ile oluşum değişkenleri ve diğer nesnelerin içindeki hesaplamalara erişmeleri sayesinde etkili kullanılabilir.

class Uygulama
  def yapılandırma
    @yapılandırma ||= Yapılandırma.new
    block_given? ? yield(@yapılandırma) : @yapılandırma
  end
end
class Yapılandırma; end

app = Uygulama.new
app.yapılandırma do |yapı|
  puts yapı.class.name
end
# Yapılandırma
#=> nil
app.yapılandırma
#=> #<Yapılandırma:0x00007f8dd7d9bce8>


Burada görüldüğü gibi yaparsak Yapılandırma sınıfından üretilen @yapılandırma nesnesi üzerinde sürekli app.yapılandırma#metod_adı gibi metodlar çağıracağımıza yield'e vereceğimiz blok içinde birçok şeyi yapabiliriz. 



-- -- Blok içindeki değişkenler

Blok içinde tanımlanan değişkenler bloğa yerel olarak kaydedilir ve bloktan çıkılınca yok edilir (metod içleri gibi). 

3.times do |x|
    puts x
end
puts x
#=> 0
# 1
# 2
# undefined (NameError)


Blok dışında tanımlanan değişkene blok içinden erişilebilir.

dış_değer = 8
3.times do |x|
  dış_değer = x
  puts x
end
puts dış_değer
#=> 0
# 1
# 2
# 2


Bu olayda bir sıradışı davranış var, blok parametresine dış değişken ile aynı isim verilmesi.

dış_değer = 8
3.times do |dış_değer|
  puts dış_değer
end
puts dış_değer
#=> 0
# 1
# 2
# 8


Bloklar bir yere saklanamazlar, çalıştırılırlar ve yok olurlar. Blokları saklamak için Proc yada Lambda kullanılır. 



-- Proc nesnesi

Örnekler:

def blok_çağır(&blok)
  blok.call
end
blok_çağır { puts "Kod bloğu"}
#=> Kod bloğu

bu_bir = proc do |*args|
  print "Müthiş ve..." unless args.empty?
  "güzel bir gün"
end

puts bu_bir       #=> #<Proc:0x...>
puts bu_bir.call  #=> güzel bir gün
puts bu_bir[1, 2] #=> Müthiş ve...güzel bir gün


Görüldüğü gibi blok_çağır metoduna çağrılacak olan bloğu & ile ifade ediyoruz. Bu hem metoda bir argüman değil bir blok beklentisini bildiriyor, hem de gelen bloğu bir Proc nesnesine dönüştürüyor. blok_çağır metodunu arkasında blok olmadan çağırırsak hata verir ama bloğu vermediğimiz yerde değil metod tanımı yapılan 2. satırda verir, bu yüzden avlaması zor olacaktır. 

bu_bir Proc nesnesinin tanımından gördüğünüz gibi metodlar gibi yıldızlı parametre kabul ediyor. Değişik çağrılarla deneme yapalım.

puts bu_bir           #=> #<Proc:0x...>
puts bu_bir.call      #=> güzel bir gün
puts bu_bir[1, 2]     #=> Müthiş ve...güzel bir gün
puts bu_bir.call 1, 2 #=> Müthiş ve...güzel bir gün
puts bu_bir["a"]      #=> Müthiş ve...güzel bir gün
puts bu_bir[]         #=> güzel bir gün


Kalıtım'la devam edeceğiz inşallah. Şimdilik kalın sağlıcakla..





Hiç yorum yok:

Yorum Gönder