5 Mart 2025 Çarşamba

Ruby Temelleri 7

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

     Selam, Ruby temel tekniklerine kaldığımız yerden devam ediyoruz. 



    Singleton Sınıflar

    Singleton (tekil) sınıfların sadece bir tane oluşum nesnesi olabilir. 

    Ruby'de 3 çeşit nesne vardır

    • Class ve Module sınıflarının oluşumu olan sınıflar ve modüller
    • Sınıfların oluşum nesneleri
    • Singleton sınıflar


    Her nesne, onun metodlarını içeren bir sınıfa sahiptir.

    class Örnek
    end

    nesne = Örnek.new

    p nesne.class   # => Örnek
    p Örnek.class   # => Class
    p Class.class   # => Class


    Nesneler kendileri metod barındıramaz, sadece sınıfları barındırabilir. Fakat tekil sınıflar yardımıyla sadece o nesneye ait metodlar tanımlayabiliriz (yani metod gene nesne içnde değil , bir sınıf içinde tanımlanır). 

    def nesne.foo
      :foo
    end
    p nesne.foo #=> :foo


    foo metodu Ruby tarafından nesne nesnesinin singleton sınıfında tanımlanır. Diğer Örnek sınıfı oluşumları bu metod çağrısına cevap vermezler.

    Ruby tekil sınıfları ihtiyaç olunca üretir. Onlara erişmeye kalkınca ya da yukarıdaki gibi metod eklemeye kalkınca Ruby otomatik olarak tekil sınıfını oluşturur. 



    -- Tekil sınıflarda kalıtım

    -- -- Altsınıf üretme aynı zamanda tekil sınıfın alt sınıfını üretir

    class Örnek
    end

    p Örnek.singleton_class #=> #<Class:Örnek>

    def Örnek.foo
      :örnek
    end

    class SubÖrnek < Örnek
    end

    p SubÖrnek.foo #=> :örnek

    p SubÖrnek.singleton_class #=> #<Class:SubÖrnek>
    p SubÖrnek.singleton_class.superclass #=> #<Class:Örnek>



    -- -- Bir modülü include ya da extend etmek tekil sınıfı genişletmez

    module ÖrnekModül
    end

    def ÖrnekModül.foo
      :foo
    end

    class Örnek
      extend ÖrnekModül
      include ÖrnekModül
    end

    Örnek.foo #=> NoMethodError: undefined method




    -- Tekil sınıflar

    Her nesne bir sınıfın oluşumudur. Ancak , tüm gerçek bu değil , Ruby'de her nesnenin ayrıca bir şekilde tekil sınıfı (singleton class) vardır. 

    Bu sayede sadece bir nesneye özel metodlar tanımlanabilir. Tekil sınıf nesne ve onun üretildiği sınıf arasında bir yerdedir. Bu yüzden tekil sınıfta tanımlanan metodlara sadece ve sadce o nesneden erişilebilir 

    nesne = Object.new

    def nesne.özel_metod
      'Sadece bu nesne bu metoda cevap verir.'
    end

    p nesne.özel_metod
    # => "Sadece bu nesne bu metoda cevap verir."

    p Object.new.özel_metod rescue p $!
    # => #<NoMethodError: undefined method `özel_metod' for #<Object:0x...>>


    Yukarıdaki örnekte metod tanımlamasını define_singleton_method metodunu kullanarak da yapabiliriz. 

    nesne.define_singleton_method :özel_metod do
      'Sadece bu nesne bu metoda cevap verir.'
    end


    Ya da daha da dolambaçlı yoldan, nesnenin tekil sınıfına metod tanımlaması göndererek.

    # send kullanılmış çünkü 'define_method' metodu private
    nesne.singleton_class.send :define_method, :özel_metod do
      "Şimdi metodu direk nesnenin tekil sınıfında tanımlıyorsunuz"
    end


    Singleton sınıf öncesinde Ruby'de metaclass adında bir yapı vardı ve ona erişmek için 

    class << nesne
      self  # nesnenin singleton_class'ına referans olur
    end

    deyim yapısı kullanılırdı. 



    -- Bir nesnenin tekil sınıfına erişmek

    Aslında iki yöntemi de daha önceki örneklerde gördük

    # metod kullanarak
    nesne.singleton_class

    # bu deyim yapısında self ifadesi ile
    class << nesne
      self  
    end



    -- Ana sınıfın değişkenine tekil sınıftan erişmek

    Tekil sınıflar bağlı oldukları nesnenin sınıf değişkenlerini sadece nesne içinde paylaşırlar. Bazı örnekler verelim.

    class Örnek
      @@foo = :örnek
    end

    class Örnek2 < Örnek
      @@foo = :örnek2
    end

    def Örnek.foo
      class_variable_get :@@foo
    end

    p Örnek.foo #=> :örnek2


    class Örnek
      def initialize
        @foo = 1
      end

      def foo
        @foo
      end
    end
     
    e = Örnek.new
     
    e.instance_eval <<-BLOCK
      def self.increase_foo
        @foo += 1
      end
    BLOCK
     
    p e.increase_foo, e.foo #=> 2 2


    Bloklar oluşum/sınıf değişkenlerine yakındır. Başka bir ortam içinde blok kullanarak oluşum veya sınıf değişkenine ulaşılamaz. class_eval'e bir string yollamak veya class_variable_get metodu kullanmak sorunu çözer.

    class Foo
      @@foo = :foo
    end

    class Örnek
      @@foo = :örnek
     
      Foo.define_singleton_method :foo do
        @@foo
      end
    end
     
    p Foo.foo #=> :örnek


    Bu hatalı sonuç veriyor ama bu

    class Foo
      @@foo = :foo
    end

    class Örnek
      @@foo = :örnek
     
      Foo.class_eval <<-BLOCK
        def self.foo
          @@foo
        end
      BLOCK
    end
     
    p Foo.foo #=> :foo

    doğru çalışıyor. Bu da sorunu çözer

    class Foo
      @@foo = :foo
    end

    class Örnek
      @@foo = :örnek
     
      Foo.define_singleton_method :foo do
        class_variable_get :@@foo
      end
    end
     
    p Foo.foo #=> :foo




    -- Tekil sınıflarda mesaj yayılması

    Oluşum nesneleri metod barındırmaz onlar sadece veri barındırırlar. Ancak bir sınıf oluşumu olan her nesne bir tekil sınıfa sahiptir. 

    Bi,r nesneye bir mesaj yollandığında (yani o nesnede bir metod çağrıldığında) Ruby ilk önce bu nesne için tanımlı bir tekil sınıf var mı? diye ve bu mesajı cevaplıyor mu? (yani orada bu metodun tanımı var mı) diye bakar. Bulamazsa nesnenin oluşturulduğu sınıf ve onun atalarına doğru mesaj yayılmaya devam eder. 

    class Örnek
      def foo
        :örnek
      end
    end

    p Örnek.new.foo #=> :örnek

    module ÖneEklenenModül
      def foo
        :önde
      end
    end

    class Örnek
      prepend ÖneEklenenModül
    end

    p Örnek.ancestors
    #=> [ÖneEklenenModül, Örnek, Object, Kernel, BasicObject]
    e = Örnek.new
    p e.foo #=> :önde

    def e.foo
      :tekil
    end

    p e.foo #=> :tekil 



    -- Tekil sınıflarda monkey patching

    Monkey patching bir kodun davranışını dinamik olarak değiştirmek anlamına geliyor. Bir tekil sınıfı tekrar açıp müdahale etmenin 3 yolu var:

    • Tekil sınıf üzerinde class_eval kullanmak
    • class << blok kullanmak
    • Nesnede metod tanımlamak için direk def ifadesi ile tekil sınıfa erişmek


    class Örnek
    end

    Örnek.singleton_class.class_eval do
      def foo
        :foo
      end
    end

    p Örnek.foo #=> :foo


    class Örnek
    end

    class << Örnek
      def bar
        :bar
      end
    end
     
    p Örnek.bar #=> :bar


    class Örnek
    end

    def Örnek.baz
      :baz
    end

    p Örnek.baz #=> :baz


    Her nesnenin kendi tekil sınıf vardır.

    class Örnek
    end

    örn1 = Örnek.new
    def örn1.foobar
      :foobar
    end
    p örn1.foobar #=> :foobar

    örn2 = Örnek.new
    örn2.foobar #=> NoMethodError





    File I/O


    -- Dosya ve giriş/çıkış işlemleri


    -- -- Parametreler

    FlagAnlamı
    "r" Read-only, sadece okunabilir, dosyanın başından başlar (default mod)
    "r+" Read-write, okuma-yazma, dosyanın başından başlar
    "w" Write-only, sadece yazma, mevcut dosyayı sıfır uzunluğa çevirir, olmayanı yeni üretir
    ve yazmak için açar
    "w+" Read-write, okuma yazma, mevcut dosyayı sıfır uzunluğa çevirir, olmayanı yeni üretir
    ve okumak-yazmak için açar
    "a" Write-only, sadece yazma, mevcut dosyanın sonundan başlar, olmayanı yeni üretir
    ve yazmak için açar
    "a+" Read-write, okuma yazma, mevcut dosyanın sonundan başlar, olmayanı yeni üretir
    ve okumak-yazmak için açar
    "b" Binary dosya modu, Windows'ta EOL <-> CRLF dönüşmesini zorlar ve aksi
    belirtilmedikçe encoding'i ASCII-8bit yapar.
    (Bu flag sadece yukarıdaki flag'larla kullanılabilir.
    Örneğin File.new("test.txt", "rb") ile test.txt dosyası sadece okunabilir modda
    binary dosya olarak açılır.)
    "t" Text dosya modu.
    (Bu flag sadece yukarıdaki flag'larla kullanılabilir.
    Örneğin File.new("test.txt", "wt") ile test.txt dosyası sadece yazılabilir modda
    text dosyası olarak açılır.)



    -- Bir dosya açmak, kapatmak

    Dosyayı elle açıp kapatmak.

    # new metodu ile
    f = File.new("test.txt", "r") # okuma
    f = File.new("test.txt", "w") # yazma
    f = File.new("test.txt", "a") # ekleme (appending)

    # open metodu ile
    f = open("test.txt", "r")

    # Çıkmadan dosyaları kapat
    f.close


    Blok kullanırsak dosya otomatik kapanır.

    f = File.open("test.txt", "r") do |f|
      # f dosyası ile birşeyler yap
      puts f.read # örneğin içindekileri oku
    end



    -- Dosya içine yazı yazmak

    Bir string dosya içine File sınıfı oluşumu nesne kullanarak yazılabilir.

    file = File.new('tmp.txt', 'w')
    file.write("NaNaNaNa\n")
    file.write('Batman!\n')
    file.close


    File sınıfı ayrıca new ve close operasyonlarını open sınıf metoduyla birleştirir. 

    File.open('tmp.txt', 'w') do |f|
      f.write("NaNaNaNa\n")
      f.write('Batman!\n')
    end


    Basit işlemlerde yazmak için direk olarak sınıf metodu olan File.write kullanılabilir. Not, bu default olarak dosyadakilerin üzerine yazar. 

    File.write('tmp.txt', "NaNaNaNa\n" * 4 + 'Batman!\n')


    File.write sınıf metodunda dosya açma modunu belirtmek için mode isimli bir anahtar ile ilave argüman olarak belirtmeliyiz.

    File.write 'tmp.txt', "NaNaNaNa\n" * 4 + 'Batman!\n', mode: 'a'



    -- Tek karakter bir girdi almak

    gerts.chomp ifadesinden farklı olarak bu yöntem Enter basılmasını beklemez. Öncelikle stdlib programımıza dahil edilmelidir.

    require 'io/console'


    Sonra bir yardımcı metod yazabiliriz.

    def get_char
      input = STDIN.getch
      control_c_code = "\u0003"
      exit(1) if input == control_c_code
      input
    end
    p get_char


    Ctrl+c tuş kombinasyonunda programdan çıkmayı eklemezsek program içinde beklerken takılma ihtimali olacağını ve görev yöneticisi falan açmak zorunda olacağımızı unutmayalım.



    -- STDIN'den değer okumak

    # STDIN'den iki sayı oku, aralarında newline olsun, ve sonucu göster
    sayı1 = gets
    sayı2 = gets
    puts sayı1.to_i + sayı2.to_i
    ## çalıştırıp değer gir:         $ ruby a_plus_b.rb
    ## veya değerleri verek çağır    $ echo -e "1\n2" | ruby a_plus_b.rb



    -- ARGV ile argümanları okumak

    Komut satırında programımızı çalıştırırken verilen argümanları ARGV özel değişkeninde görebiliriz.

    sayı1 = ARGV[0]
    sayı2 = ARGV[1]
    puts sayı1.to_i + sayı2.to_i
    ## çalıştırmak için konsolda: $ ruby a_plus_b.rb 1 2






    Queue nesnesi

    Queue nesnesi bir yığın nesnesi içine yerleştirilen nesneler, özellikle paralel işlemlerde veriye sağlıklı erişmek amacıyla kullanılır. Deyimleri şunlar:

    q = Queue.new
    q.push nesne
    q << nesne # 'push' ile aynı
    q.pop #=> nesne




    -- Çok çalışan , tek gider

    Birçok çalışandan gelen veriyi toplamak istiyoruz. Öncelikle bir Queue nesnesi tanımlarız.

    gider = Queue.new

    Sonra 16 çalışan aynı gidere rastgele sayılar gönderir. 

    (1..16).to_a.map do
      Thread.new do
        gider << rand(1..100)
      end
    end.map(&:join)

    Veriyi almak için Queue nesnesini array'e çeviririz.

    veri = [].tap { |a| a << gider.pop until gider.empty? }



    -- Bir Queue nesnesini Array nesnesine dönüştürmek


    q = Queue.new
    q << 1
    q << 2

    a = Array.new
    a << q.pop until q.empty?

    Ya da tek satır olarak

    [].tap { |array| array << q.pop until q.empty? }



    -- Çok çalışan , tek kaynak

    Veriyi paralel işlemek istiyoruz. Önce biraz veri oluşturalım.

    kaynak = Queue.new
    veri = (1..100)
    veri.each { |e| kaynak << e }

    Sonra veriyi işleyecek bir kısım çalışanlar üretiyoruz.

    (1..16).to_a.map do
      Thread.new do
        until kaynak.empty?
          item = kaynak.pop
          sleep 0.5
          puts "İşlenen: #{item}"
        end
      end
    end.map(&:join)

    Paralel çalışan prosesler veriye rastgele eriştiği halde işlenmemiş hiç bir veri kalmıyor. 



    -- Tek kaynak - Çalışanlar hattı - Tek gider

    Verileri birçok paralel çalışan ile işleyip bir hedefe koyacağız.

    Çalışanlar hem veriyi tüketip hem veri üreteceği için iki Queue nesnemiz olmalıdır. 

    ilk_giriş_kaynağı = Queue.new
    ilk_çıkış_gideri  = Queue.new
    100.times { |i| ilk_giriş_kaynağı << i }

    Çalışanların ilk yaptığı ilk_giriş_kaynağı nesnesinden veriyi okumak, sonraki işi ise bu veriyi işleyip ilk_çıkış_gideri  nesnesine koymak. 

    (1..16).to_a.map do
      Thread.new do
        loop do
          item = ilk_giriş_kaynağı.pop
          ilk_çıkış_gideri << item ** 2
          ilk_çıkış_gideri << item ** 3
        end
      end
    end

    İkinci bir grup çalışansa ilk_çıkış_gideri'ni veri kaynağı olarak alıp elemanlarını işler ve ikinci_çıkış_gideri nesnesine kaydeder.

    ikinci_giriş_kaynağı = ilk_çıkış_gideri
    ikinci_çıkış_gideri  = Queue.new

    (1..32).to_a.map do
      Thread.new do
        loop do
          item = ikinci_giriş_kaynağı.pop
          ikinci_çıkış_gideri << item * 2
          ikinci_çıkış_gideri << item * 3
        end
      end
    end

    Şimdi ikinci_çıkış_gideri en son işlenmiş veriyi saklıyor onu da bir array'e dönüştürelim.

    sleep 5 # senkronizasyon için biraz oyalan
    gider = ikinci_çıkış_gideri
    a = [].tap { |a| a << gider.pop until gider.empty? }



    -- Bir Queue nesnesine veri basmak - push


    q = Queue.new
    q << "başka bir queue dahil herhangi bir nesne"
    # veya
    q.push :veri

    • Üst sınır yok, Queue nesnesi sonsuza kadar büyüyebilir.
    • #push asla engellenmez



    -- Bir Queue nesnesinden veri çekmek - pop


    q = Queue.new
    q << :data
    q.pop #=> :data

    • #pop kullanılabilir herhangi bir veri olana kadar engellenir
    • #pop senkronizasyon için kullanılabilir.

    q = Queue.new
    q << :data
    q.pop #=> :data
    q.pop #=> No live threads left. Deadlock? (fatal)

    Eğer Thread içinde kullanılırsa pop satırında bekler. 



    -- Senkronizasyon - Zamanlama yönetimi

    Thread içinde pop metodunun bekletilmesini senkronizasyon amaçlı kullanabiliriz.

    senkroncu = Queue.new

    a = Thread.new do
      puts "İlk thread girdi"
      senkroncu.pop
      puts "Bu sonradan oluşur"
    end

    b = Thread.new do
      puts "Bu önce oluşur"
      STDOUT.flush
      senkroncu << :ok
    end

    [a, b].map(&:join)

    Çıktısı

    İlk thread girdi
    Bu önce oluşur
    Bu sonradan oluşur

    İlk thread çalışmaya başlıyor ama senkroncu nesnesinde henüz bir veri olmadığı için pop metodunun çağrıldığı yerde bekletiliyor. Bu arada ikinci thread giriyor ve senkroncu nesnesine yeni bir veri ekliyor. Arkasından ilk thread pop satırında artık veri alabildiği için çalışmaya devam ediyor.



    -- İki Queue nesnesini birleştirmek

    • Sonsuz engellemeye mani olmak için birleştirme yapılan thread içinde okuma da yapılmamalıdır.
    • Bir queue nesnesinde veri bitince diğerinin de okuması beklemesin diye birleşecek Queue nesnelerinden okumalar farklı Thread içinde yapılmalıdır.

    Önce iki Queue nesnesi üreterek içlerine veri koyalım.

    q1 = Queue.new
    q2 = Queue.new
    (1..100).each { |e| q1 << e }
    (101..200).each { |e| q2 << e }

    Yeni bir Queue nesnesi oluşturup diğerlerinin içindeki verileri ona aktaralım.

    birleşik = Queue.new

    [q1, q2].map do |q|
      Thread.new do
        until q.empty?
          birleşik << q.pop
        end
      end
    end.map(&:join)

    Eğer her iki Queue nesnesini de tamamen boşaltabileceğinizden eminseniz (tüketme hızı ekleme hızından yüksektir) daha basit bir yol da var.

    birleşik = Queue.new
    birleşik << q1.pop until q1.empty?
    birleşik << q2.pop until q2.empty?

    Eğer paralelde başka thread'lerin q1 veya q2'ye daha hızlı veri yüklemediğinden eminseniz bu yöntem daha kısa .




    Küme Dağıtma

    Bir küme veriyi dağıtma işleminin çoğu sihirli * operatörü ile yapılır.

    ÖrnekSonuç / yorum
    a, b = [0,1] a=0, b=1
    a, *gerisi = [0,1,2,3] a=0, gerisi=[1,2,3]
    a, * = [0,1,2,3] a=0 .first metoduna eşdeğer
    *, z = [0,1,2,3] z=3 .last metoduna eşdeğer



    -- Blok argümanlarını dağıtmak

    Aşağıdaki yöntemlerle blok argümanlarını dağıtabiliriz.

    üçlüler = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

    üçlüler.each { |(ilk, ikinci, üçüncü)| puts ikinci }
    # 2
    # 5
    # 8

    üçlüler.map { |(ilk, *gerisi)| gerisi.join(' ') }
    # => ["2 3", "5 6", "8 9"]





    IRB Kullanmak

    IRB Interactive Ruby Shell demek. Normal bir terminalin işletim sistemi komutlarını canlı olarak işlemesi gibi IRB de Ruby komutlarını canlı olarak denemenize imkan tanır. 

    Ruby API ile çalışırken IRB vazgeçilemez bir araçtır. Küçük Ruby kodlarının nasıl tepki vereceklerini test etmek için idealdir. 

    IRB konsolunun en önemli özelliklerinden biri metod isimlerini yazarken size bir liste göstermesi ve listedeki seçenekler arasında Tab tuşu ile seçim yapabilmenizi sağlamasıdır. 


    Parametreleri

    OpsiyonDetaylar
    -f ~/.irbrc dosyasının okunmasını engeller
    -m Bc mod (mathn kütüphanesi yüklenir, kesirler ve matrix kullanılabilir)
    -d $DEBUG değerini true yapar ('ruby -d' ile aynı)
    -r load-module 'ruby -r' ile aynı
    -I path $LOAD_PATH klasörünü belirler
    -U 'ruby -U' ile aynı
    -E enc 'ruby -E' ile aynı
    -w 'ruby -w' ile aynı
    -W[level=2] 'ruby -W' ile aynı
    –-inspect Çıktılar için "inspect" kullan (default - Bc mod harici)
    --noinspect Çıktılar için "inspect" kullanma
    --readline Readline genişleme modülünü kullan
    --noreadline Readline genişleme modülünü kullanma
    --prompt prompt-mode Prompt modu değiştir. Ön-tanımlı modlar 'default', 'simple', 'xmp' ve 'inf-ruby'
    –-inf-ruby-mode emac'lerde prompt'u uygun olan "inf-ruby-mode" a geçirir --readline'ı baskılar
    –-simple-prompt Basit kısa prompt modu
    –-noprompt Prompt yok modu
    –-tracer Her komut çalıştırılmasında trace göster
    –-back-trace-limit n Back-trace gösterirken n tane geri git Default 16
    –-irb_debug n Dahili debug seviyesi n yap (popüler bir kullanım değil)
    -v, –-version IRB versiyonunu yaz



    -- Temel irb kullanımı

    Komut satırında irb yazarak programı çalıştırırız. Basit Ruby komutları girebiliriz.

    $ irb
    irb(main):001:0> 2 + 2
    => 4
    irb(main):002:0>

    Metodlar gibi karmaşık blokları da girebiliriz.

    irb(main):002:1* def metod
    irb(main):003:1*   puts "Merhaba Dümya"
    irb(main):004:0> end
    => :metod
    irb(main):005:0> metod
    Merhaba Dümya
    => nil



    -- Ruby betiğinde IRB oturumu çalıştırmak

    Ruby 2.4.0'dan itibaren Ruby programımız içinde IRB oturumu başlatabiliriz.

    require 'irb'
    binding.irb

    Bu programın bulunulan noktasında bir irb oturumu açar. Burada betiğimizde o andaki değişkenlere erişip kullanabiliriz. Debug işlemleri için çok faydalıdır.

    $ ruby run_irb.rb

    From: run_irb.rb @ line 2 :

        1: require 'irb'
     => 2: binding.irb
    irb(main):001:0> exit

    Çıkmak için exit komutu girin ya da Ctrl+D tuşlarına basın. Betik kaldığı yerden devam eder.

    Bu bölüme de bu kadar yeter, oldukça uzadı. Sonraki bölümde Enumerator sınıfı üzerinde duracağız inşallah. Şimdilik kalın sağlıcakla..






    Hiç yorum yok:

    Yorum Gönder