15 Mart 2025 Cumartesi

Ruby Temelleri 9

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

     

    Selam, Ruby temellerini öğrenmeye devam ediyoruz.



    Örtülü Alıcılar ve Self


    -- Her zaman örtülü bir alıcı vardır

    Ruby'de bir metod çağırdığımızda, o metodun mutlaka bir alıcısı (gönderildiği nesne) vardır. Eğer bu alıcı net olarak belirtilmiyorsa Ruby bu örtülü alıcıyı self değerinde saklar. class ve module deyimleri de self değerinin işaret ettiği nesneye göre değişir. Bu davranışları anlamak Ruby dilini anlamamızda çok yardımcı olacaktır.

    Örneğin bir IRB oturumunu ilk açtığımızda,

    >> self
    => main

    Bu görünüşe göre metodların örtülü alıcısı main nesnesi. Bu noktada tanımlayacağımız metodlar main nesnesinin metodları olacaktır. 

    ?> def foo(arg)
    ?>   arg.to_s
    >> end
    => :foo
    >> foo 1
    => "1"


    Bu kod foo metodunu main nesnesinde tanımlar. 

    Not olarak yerel değişkenler metod isimlerinden önce değerlendirilir. Eğer metod ile aynı isimde bir yerel değişken tanımlamışsak önce yerel değişkene bakılır. Önceki örnekten devam edelim.

    >>  defined? foo
    => "method"
    >> foo = 1
    => 1
    >> defined? foo
    => "local-variable"
    >> foo
    => 1
    >> method :foo
    => #<Method: Object#foo(arg) (irb):2>


    Önce değişken sonra metod tanımlanmış olsa da farketmez, aynı isimlerde önce değişken var mı diye bakılır. Ama metod hala tanımlı duruyor.

    >> foo
    => 1
    >> foo 2
    => "2"
    >> foo()
    => (ArgumentError) (given 0, expected 1)


    Yani ille de metodu çağırmak istersek parantezli yazabiliriz. Bir dikkat ettiyseniz foo 2 şeklinde argümanla beraber yazınca yine metod olduğunu algıladı. 




    -- Örtülü alıcıyı değiştiren deyimler

    Bir sınıf ya da modül tanımlarken , tanımlama bloğu içinde self kelimesi içinde bulunduğu sınıf ya da modülü ifade eder.

    puts "Ben, #{self}"     # Ben, main
    class Örnek
      puts "Ben, #{self}"   # Ben, Örnek
      def yaz
        puts "Ben, #{self}" # Ben, #<Örnek:0x00007fefcf9763e8>
      end
    end

    Örnek.new.yaz




    -- self ne zaman kullanılır?

    Birçok Ruby programı örtülü alıcıyı kodlarında kullanır. Ruby'ye yeni başlayanlar için self değerini anlamak biraz karmaşık gelebilir. self kullanmanın iki temel nedeni vardır.


    -- -- Alıcıyı değiştirmek

    Bir sınıf ya da modül içinde düz def bloğu ile metod tanımlamak bir oluşum metodu tanımlar. Sınıf metodu tanımlamak için self deyimi kullanılır. 

    class Foo
      def bar
        1
      end

      def self.bar
        2
      end
    end

    Foo.new.bar #=> 1
    Foo.bar #=> 2


    Sınıf kodunun root'unda self kelimesi sınıfı ifade ettiği için def self.bar satırı bir sınıf metodu tanımlaması yapar. 



    -- -- Alıcı belirsizliğini gidermek

    Yerel değişken ile metod aynı ismi paylaşırsa, self ile örtülü alıcı netleştirilebilir. 

    class Example
      def foo
        1
      end

      def bar
        foo + 1
      end

      def baz(foo)
        self.foo + foo # self.foo bir metod, foo ise yerel değişken
      end

      def qux
        bar = 2
        self.bar + bar # self.bar bir metod, bar ise yerel değişken
      end
    end

    Example.new.foo    #=> 1
    Example.new.bar    #=> 2
    Example.new.baz(2) #=> 3
    Example.new.qux    #=> 4


    Diğer bir alıcı belirsizliği giderme ihtiyacı da eşittir işareti ile biten metodlarda olabilir.

    class Example
      def foo=(input)
        @foo = input
      end

      def get_foo
        @foo
      end

      def bar(input)
        foo = input # foo yerel değişkeni üretir
      end

      def baz(input)
        self.foo = input # foo= metodunu çağırır
      end
    end

    e = Example.new
    e.get_foo #=> nil
    e.foo = 1
    e.get_foo #=> 1
    e.bar(2)
    e.get_foo #=> 1
    e.baz(2)
    e.get_foo #=> 2





    Monkey patching

    Monkey patching bir kod parçasının davranışını çalışma zamanında dinamik olarak değiştirmek için kullanılan bir terimdir. Teknik olarak Ruby'de bir sınıfa sonradan bir metod ekleyebilir ya da mevcut bir metodun üzerine yazarak davranışını değiştirebiliriz. 



    -- Mevcut Ruby metodunu değiştirmek

    Üzerine yazarak mevcut metodun davranışını değiştirebiliriz.

    puts "Selam millet".reverse # => "tellim maleS"

    class String
      def reverse
        "Selam illet"
      end
    end

    puts "Selam millet".reverse # => "Selam illet"


    String sınıfını tekrar açıp reverse metodu üzerine yazıyoruz ve dönen cevabı değiştiriyoruz. Ama bu durumda tüm string değerler için aynı sonucu verir.

    puts "Administrator".reverse # => "Selam illet"


    Sadece "Selam millet" değeri gelince değişik davransın, diğer string değerlerde normal çalışsın dersek.

    puts "Selam millet".reverse # => "tellim maleS"

    class String
      alias real_reverse reverse
      def reverse
        self == "Selam millet" ? "Selam illet" : real_reverse
      end
    end

    puts "Selam millet".reverse # => "Selam illet"
    puts "Administrator".reverse # => "rotartsinimdA"


    Üzerine yazmadan önce orijinal metodu alias ile başka bir isimle yedeğe alıyoruz, sonra istersek orijinali çağırıyoruz.



    -- Parametreli metodu değiştirmek

    Üzerine yazdığımız metodla aynı kapsamı kullanırız. 

    class Gemicik
      def initialize(isim)
        @isim = isim
      end

      def isim
        @isim
      end
    end

    puts Gemicik.new("Gemi").isim # => "Gemi"

    class Gemicik
      def isim
        "⛵ #{@isim} ⛵"
      end
    end

    puts Gemicik.new("Memiii").isim # => "⛵ Memiii ⛵"




    -- Refinement ile güvenli monkey patch

    Öncelikle ayar çekmek için bir modül tanımlıyoruz. 

    module StringAyar
      refine String do
        def reverse
          "Merhaba illlet"
        end
      end
    end


    refine metodu yardımıyla verilen sınıfta yani String sınıfında reverse metodu davranışını değiştiriyoruz. Şimdi iki değişik sınıfta bu modülü kullanarak nelerin değiştiğine bakalım. 

    class MPolanSınıf
      using StringAyar

      def initialize(str)
        @str = str
      end
       
      def reverse
        @str.reverse
      end
    end

    class MPolmayanSınıf  
      def initialize(str)
        @str = str
      end
       
      def reverse
        @str.reverse
      end
    end

    p MPolanSınıf.new("Merhaba millet").reverse
    #=> "Merhaba illlet"

    p MPolmayanSınıf.new("Merhaba millet").reverse
    #=> "tellim abahreM"


    Kökten String#reverse metodu üzerine yazmayıp sadece ayar modülümüzü kullanan sınıf içinde davranışının değişmesini sağlıyoruz. MPolanSınıf sınıfı dışında String#reverse metodu hala orijinali olarak çalışmaya devam eder.




    -- Herhangi bir metodu değiştirmek

    Metod tanımını tekrar yazdığımız satırdan itibaren metodun davranışı değişir.

    def merhaba
      puts "Selam millet"
    end

    merhaba # => "Selam millet"

    def merhaba
      puts "Selam illet"
    end

    merhaba # => "Selam illet"




    -- Mevcut sınıfa yeni metod eklemek

    Mevcut bir sınıfa istediğimiz yeni bir metod da ekleyebiliriz.

    class String
      def süsle
        "~~~{#{self}}~~~"
      end
    end

    puts "Ümit".süsle # => "~~~{Ümit}~~~"

    gibi.





    İç Gözlem


    -- Metodlarını görmek

    -- -- Bir nesneyi incelemek

    Bir nesneye ait public metodları methods veya public_methods metod çağrılarına verdiği cevaptan görebiliriz. Metod isimlerini içeren bir array döner.

    class Foo
      def bar; 42; end
    end
    f = Foo.new
    def f.yay; 17; end
    p f.methods.sort
    # [:!, :!=, :!~, :<=>, :==, :===, :__id__, :__send__, :bar,
    #  :class, :clone, :define_singleton_method, :display, :dup,
    #  :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash,
    #  :inspect, :instance_eval, :instance_exec, :instance_of?,
    #  :instance_variable_defined?, :instance_variable_get,
    #  :instance_variable_set, :instance_variables, :is_a?, :itself,
    #  :kind_of?, :method, :methods, :nil?, :object_id, :private_methods,
    #  :protected_methods, :public_method, :public_methods, :public_send,
    #  :remove_instance_variable, :respond_to?, :send, :singleton_class,
    #  :singleton_method, :singleton_methods, :tap, :then, :to_enum, :to_s,
    #  :yay, :yield_self]


    Diğer nesnelerle ortak olmayan metodları bulmak için.

    p (f.methods - Object.methods).sort
    #=> [:bar,:yay]


    Alternatif olarak methods ya da public_methods'a false argüman vererek de filtreleyebiliriz. 

    p f.methods(false) # public ve protected singleton metodlar
    #=> [:yay]

    p f.public_methods(false)
    #=> [:yay, :bar]


    Bir nesnenin private metodlarını private_methods, protected metodlarını protected_methods ile görebiliriz. 

    p f.private_methods.sort
    # [:Array, :Complex, :Float, :Hash, :Integer, :Rational, :String,
    #  :__callee__, :__dir__, :__method__, :`, :abort, :at_exit, :autoload,
    #  :autoload?, :binding, :block_given?, :caller, :caller_locations,
    #  :catch, :eval, :exec, :exit, :exit!, :fail, :fork, :format, :gem,
    #  :gem_original_require, :gets, :global_variables, :initialize,
    #  :initialize_clone, :initialize_copy, :initialize_dup, :iterator?,
    #  :lambda, :load, :local_variables, :loop, :method_missing, :open,
    #  :p, :pp, :print, :printf, :proc, :putc, :puts, :raise, :rand, :readline,
    #  :readlines, :require, :require_relative, :respond_to_missing?, :select,
    #  :set_trace_func, :singleton_method_added, :singleton_method_removed,
    #  :singleton_method_undefined, :sleep, :spawn, :sprintf, :srand, :syscall,
    #  :system, :test, :throw, :trace_var, :trap, :untrace_var, :warn]
    p f.protected_methods
    #=> []


    Aynı methods ve public_methods gibi false argüman vererek private_methods ve protected_methods ile de kalıtımdan gelen metodları filtreleyebiliriz.




    -- -- Sınıf ya da modülü incelemek

    Sınıflar ve modüller yukarıda gördüğümüz metodlara karşılık gelen instance_methods, public_instance_methods, protected_instance_methods, ve private_instance_methods metodlarını kendilerinin oluşum nesnelerinin metodlarıni göstermek için kullanırlar. Aynı şekilde false argüman alabilirler. 

    p Foo.instance_methods.sort
    # [:!, :!=, :!~, :<=>, :==, :===, :__id__, :__send__, :bar, :class,
    #  :clone, :define_singleton_method, :display, :dup, :enum_for, :eql?,
    #  :equal?, :extend, :freeze, :frozen?, :hash, :inspect, :instance_eval,
    #  :instance_exec, :instance_of?, :instance_variable_defined?,
    #  :instance_variable_get, :instance_variable_set, :instance_variables,
    #  :is_a?, :itself, :kind_of?, :method, :methods, :nil?, :object_id,
    #  :private_methods, :protected_methods, :public_method, :public_methods,
    #  :public_send, :remove_instance_variable, :respond_to?, :send, :singleton_class,
    #  :singleton_method, :singleton_methods, :tap, :then, :to_enum, :to_s, :yield_self]
    p Foo.instance_methods(false)
    # [:bar]


    Son olarak bu metodların isimlerini ilerde hatırlamak isterseniz bir regexp filtrelemesi yapabilirsiniz.

    p f.methods.grep(/methods/)
    # [:methods, :singleton_methods, :protected_methods,
    #  :private_methods, :public_methods]

    p Foo.methods.grep(/methods/)
    # [:instance_methods, :protected_instance_methods, :public_instance_methods,
    #  :undefined_instance_methods, :private_instance_methods, :methods,
    #  :singleton_methods, :protected_methods, :private_methods, :public_methods]




    -- Bir nesnenin oluşum değişkenlerini görmek

    Bir nesnenin oluşum değişkenlerini görmek için instance_variables, instance_variable_defined?, ve instance_variable_get metodları kullanılır. Değerlerini değiştirmek için de instance_variable_set ve remove_instance_variable metodları kullanılır.

    class Foo
      attr_reader :bar
      def initialize
        @bar = 42
      end
    end
    f = Foo.new
    p f.instance_variables                #=> [:@bar]
    p f.instance_variable_defined?(:@baz) #=> false
    p f.instance_variable_defined?(:@bar) #=> true
    p f.instance_variable_get(:@bar)      #=> 42
    f.instance_variable_set(:@bar, 17)    
    p f.bar                               #=> 17
    f.remove_instance_variable(:@bar)    
    p f.bar                               #=> nil
    p f.instance_variables                #=> []


    Oluşum değişkeni işleme metodları oluşum değişkeni dışarıdan okunamasa bile çalışır. Mesela yukarıdaki kodda attr_reader :bar satırı olmasa p f.bar satırı oluşum değişkeni @bar'ı okuyamaz hata verir. Ancak p f.instance_variable_get(:@bar) hala @bar değişkenine erişebilir. 

    Metodlara oluşum değişkeni adını girerken başında @ olmasını unutmayın yoksa hata verir.

    f.instance_variable_defined?(:jim)
    #=> NameError: `jim' is not allowed as an instance variable name




    -- Global ve yerel değişkenleri görmek

    Kernel modülü metodları global_variables ve local_variables global ve yerel değişkenleri görmek için kullanılır. 

    cats  = 42
    $demo = "in progress"
    p global_variables.sort
    # [:$!, :$", :$$, :$&, :$', :$*, :$+, :$,, :$-0, :$-F, :$-I, :$-W,
    #  :$-a, :$-d, :$-i, :$-l, :$-p, :$-v, :$-w, :$., :$/, :$0, :$:, :$;,
    #  :$<, :$=, :$>, :$?, :$@, :$DEBUG, :$FILENAME, :$LOADED_FEATURES,
    #  :$LOAD_PATH, :$PROGRAM_NAME, :$VERBOSE, :$\, :$_, :$`, :$demo,
    #  :$stderr, :$stdin, :$stdout, :$~]

    p local_variables
    #=> [:cats]


    Oluşum değişkenlerinden farklı olarak global ve yerel değişkenlerin get/set metodları yok , standart yol olan okuma ve atama yapısı kullanılır. Ama ille de dolambaçlı yoldan gideyim derseniz eval metodunu kullanabilirsiniz. 

    var = "$demo"
    p eval(var)           #=> "in progress"
    eval("#{var} = 17")
    p $demo             #=> 17


    Default olarak eval , değişkenleri bulunulan kapsamda değerlendirir. Eğer başka bir kapsamdan değerlendirmek istersek o kapsamın binding nesnesine ihtiyacımız var. 

    def local_variable_get(isim, kapsam=nil)
      foo = :içerdeki
      eval(isim, kapsam)
    end

    def test_1
      foo = :dışardaki
      p local_variable_get("foo")
    end

    def test_2
      foo = :dışardaki2
      p local_variable_get("foo", binding)
    end
     
    test_1 #=> :içerdeki
    test_2 #=> :dışardaki2


    Bu örnekte test_1 metodu local_variable_get metodunu çağırırken kapsam belirtmediği için local_variable_get  metodu kendi tanımındaki foo değişkenini geri dönüyor ve :içerdeki değeri dönüyor. Ancak test_2 metodu  local_variable_get metodunu çağırırken kendi kapsamını binding metodu ile öğrenip gönderiyor ve metod test_2 metod tanımı içindeki foo değişkeni değeri olan :dışardaki2 değerini dönüyor.




    -- Sınıf değişkenlerini görmek

    Sınıflar ve modüller de diğer nesneler gibi oluşum değişkenlerini okuyan aynı metodlara sahiptir. Sınıflar ve modüller ayrıca sınıf değişkenlerine erişen metodlara da sahiptir ( @@böyle_isimliler ).

    p Module.methods.grep(/class_variable/)
    #=> [:class_variables, :class_variable_get, :remove_class_variable,
    #=>  :class_variable_defined?, :class_variable_set]

    class Foo
      @@instances = 0
      def initialize
        @@instances += 1
      end
    end

    class Bar < Foo; end

    5.times{ Foo.new }
    3.times{ Bar.new }
    p Foo.class_variables                   #=> [:@@instances]
    p Bar.class_variables                   #=> [:@@instances]
    p Foo.class_variable_get(:@@instances)  #=> 8
    p Bar.class_variable_get(:@@instances)  #=> 8


    Aynı oluşum değişkeni metodlarında olduğu gibi sınıf değişkenlerinde de @@ koymadan isimi yazarsak hata verir. 





    Recursion

    Rekürsiyon tekrarlama demek. Programcılıkta rekürsiyon belli bir amaca ulaşana kadar fonksiyonun kendi kendisini tekrar tekrar çağırması demek. Buna en uygun örnek olarak ya faktöriyel ya da fibonacci dizisi verilir hep. Faktöriyel örneğine bir bakalım. 

    def fact(n)
      return 1 if n <= 1
      n * fact(n-1)
    end

    p fact(4) #=> 24


    Faktöriyel verilen sayıdan 1'e inene kadar tüm sayıların birbiri ile çarpımı demek. Yani 

    4! = 4 * 3 * 2 * 1 => 24


    Buradan gördüğümüz bir başka şey de.

    n! = n * (n-1)!


    Metodumuz da tam bunu yapıyor 1'e ulaşana kadar kendini çağırıp duruyor. Şimdi olanları adım adım hayal edelim.

    fact(4)
    #=> 4 * fact(3)
    #=> 4 * ( 3 * fact(2) )
    #=> 4 * ( 3 * ( 2 * fact(1) ) )
    #=> 4 * ( 3 * ( 2 * 1 ) )
    #=> 4 * ( 3 * 2 )
    #=> 4 * 6
    #=> 24


    Ruby kodu işlerken aynı yukarıda gösterildiği gibi parantez içinde parantez işlem yapıyor. Her parantez içindeki işlem sonucu bir yığına atılarak devam ediliyor. Faktöriyelini aldığımız sayı büyüdükçe bu iç içe parantezlerin yani yığına atılan bilgilerin sayısı artıyor. Çok büyük sayılarda sistem yığını dolup SystemStackError  hatası oluşabilir. 



    -- Tail recursion

    Yukarıdaki hatanın oluşmaması için Tail recursion adı verilen bir teknik uygulanır. Ara değerleri bir yerde toplayarak hafıza kullanımını azaltabiliriz. 

    def fact(n, acc=1)
      return acc if n <= 1
      fact(n-1, n*acc)
    end


    Bu döngünün kullandığı hafızayı incelersek.

    fact(4)
    #=> fact(4, 1)
    #=> fact(3, 4)
    #=> fact(2, 12)
    #=> fact(1, 24)
    #=> 24

    şeklinde olduğunu görürüz. Bu teknik daha az hafıza harcıyor ama bu da her adımda yapılan metod çağrılarının sonuçlarını onu çağıran metoda dönebilmek için call stack adı verilen bir yığında takip için bulundurmaya devam ediyor. 

    Bana sorarsanız rekürsif metod çağrısı kullanmak yerine eynı işi yapacak bir standart döngü tanımlamak. Ama yine de çok isterseniz şu dokümanda anlatılıyor. 

    def fact(n)
      res = 1
      while n >= 1 do
        res *= n
        n -= 1
      end
      return res
    end





    ERB

    Erb - Embedded Ruby teriminden bir kısaltma. Şablonların içine Ruby kodu enjekte etmek için kullanılır. Bu şablonların en çok kullanılanı HTML ve YAML şablonlar. ERB bir kurala göre yazılmış yazılar içinde Ruby kodu çalıştıran bir Ruby sınıfıdır. 

    Bir şablon metin içinde ERB şu kurallara göre kullanılır:

    • <% number = rand(10) %>  -  bu kod sadece çalıştırılır ama bir şey göstermez
    • <%= number %> - Bu kod çalıştırılır ve sonucu o noktaya yazılır.
    • <%# yorum yazısı %> - Bu bir yorum, kod olarak işlenmez de bir şey yazmaz da.

    ERB kullanan şablon dosyalarında dosya uzantısına .erb eklenmesi bir gelenektir. Mesela .js.erb, .html.erb, .css.erb gibi. 

    Basit bir örnek.

    require 'erb'

    x = 42
    şablon = ERB.new <<-EOF
      x değeri: <%= x %>
    EOF
    puts şablon.result #=>      x değeri: 42


    ERB kullanımı en çok Rails gibi web uygulama geliştirme ortamlarında kullanılır. Örnek olarak bir liste yazan ve erb kullanan bir HTML kodu düşünelim.

    <ul>
       <% @ürünler.each do |ürün| %>
          <li><%= ürün %></li>
       <% end %>
    </ul>


    2. ve 4. satırlar kod olarak işleniyor fakat çıktıya bir şey yazmıyor, onlar sadece döngüyü oluşturuyor. 3. satırda ise çıktıya yazan bir kod var. Şimdi çıktısını görelim

    require 'erb'

    @ürünler = ["Elma", "Armut", "Kiraz"]
    şablon = ERB.new <<-EOF
    <ul>
       <% @ürünler.each do |ürün| %>
          <li><%= ürün %></li>
       <% end %>
    </ul>
    EOF
    puts şablon.result


    Çalıştırdığımızda konsola yazdığı çıktı.

    <ul>

          <li>Elma</li>

          <li>Armut</li>

          <li>Kiraz</li>

    </ul>


    Gördüğümüz üzere <li> tag'leri de yazıyor. Mesela ürünler listesi boş olsa çıkıtı.

    @ürünler = []
    ....
    <ul>

    </ul>


    Nasip olur da bir Rails dökümanı daha hazırlarsam bu konuda bir sürü örnek oluşacak mutlaka. Ama şimdilik bloğumda daha önce yazdığım RubyOnRails kategorisi yazılarda bir çok örnek kullanımları var. 





    Rastgele Sayılar

    Ruby rand metodu 0.0 ile 1.0 arasında sayı üretilir (1.0 hariç). bazı kullanım örnekleri.

    >> rand
    => 0.41749678055181516

    >> rand * 100
    => 41.27381953719137

    >> rand 100 # 0..99 arası tamsayı
    => 92

    >> rand 1..10 # 1..10 aralığında tamsayı
    => 6

    >> rand 2.0..7.0 # float aralık
    => 6.210453511729288

    >> rand(97..101).chr # doğru cevabı bulur
    => "c" 


    Sanırım oldukça sayıda Ruby teknikleri üzerinde durduk. Bana yardımcı olduğu kadar sizlere de yardımcı olmasını temenni ederim. Başka yazılarda buluşmak ümidiyle, şimdilik kalın sağlıcakla..









    Hiç yorum yok:

    Yorum Gönder