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