9 Mart 2025 Pazar

Ruby Temelleri 8

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

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



Enumerator Sınıfı

Bir Enımerator elemanlarına iterasyon uygulanabilen bir nesnedir. Bir koşul karşılanana kadar döngüye girmek yerine, nesne gerektiği gibi eleman değerlerini sıralar (enumerates). 


-- Kendi Enumeratörümüz

Fibonachi sayıları için bir enumeratör yapalım.

fibonacci = Enumerator.new do |yielder|
  a = b = 1
  loop do
    yielder << a
    a, b = b, a + b
  end
end

 Şimdi fibonacci nesnesinde her Enumerable metodunu çalıştırabiliriz. 

fibonacci.take 10
#=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

Sınırsız bir döngü oluşturduk, umarım yanlışlıkla tüm elemanları istemeye kalkmayız. 

Daha basit bir örnek verelim.

class Foo
  include Enumerable
  def each
    yield 1
    yield 1, 2
    yield
  end
end
Foo.new.each_entry{ |eleman| p eleman }
# 1
# [1, 2]
# nil
Foo.new.each{ |eleman| p eleman }
# 1
# 1
# nil




-- Mevcut metodlar

Örneğin each gibi bir iterasyon metodu blok verilmeden çağrıldığında bir Enumerator nesnesi dönmelidir. Bunu enum_for metodu ile yapabiliriz.

def each
  return enum_for :each unless block_given?

  yield :x
  yield :y
  yield :z
end

Bu sayede programlayıcı enumerable metodlarını zincirleme kullanabilir.

p ("a".."d").each #=> #<Enumerator: "a".."d":each>
p ("a".."d").each.drop(2) #=> ["c", "d"]
p ("a".."d").each.drop(2).map(&:upcase) #=> ["C", "D"]
p ("a".."d").each.drop(2).map(&:upcase).first #=> "C"



-- Rewind işlemi

Enumerator'u tekrar başlatmak için rewind metodu kullanılır.

= Enumerator.new do |yielder|
  x = 0
  loop do
    yielder << x
    x += 1
  end
end

p .next  #=> 0

p .next  #=> 1

p .next  #=> 2

.rewind

p .next  #=> 0





C Genişlemeleri

C genişlemeleri iki ana parçadan oluşur.

  • C kodunun kendisi
  • Genişleme konfigürasyon dosyası

İlk genişlememizi yazmak için aşağıdaki kodu extconf.rb adında bir dosya içine yazalım.

extconf.rb

require 'mkmf'

create_makefile('hello_c')

Dikkat edilmesi gereken noktalar var.

İlki hello_c ismi derlenmiş genişlememizin alacağı isim. require ile birlikte kullandığımız şey olacaktır.

İkincisi extconf.rb dosyası ismi herhangi bir şey olabilir, ama native gem tasarlarken tercih edilen isimdir. 

Şimdi C kodumuzu hello.c isimli dosyanın içine yazalım. Sonrada ruby extconf.rb && make komutunu terminalde çalıştıralım.

hello.c

#include <stdio.h>
#include "ruby.h"

VALUE world(VALUE self) {
  printf("Hello World!\n");
  return Qnil;
}

// Bu modül için başlangıç metodu
void Init_hello_c() {
  VALUE HelloC = rb_define_module("HelloC");
  rb_define_singleton_method(HelloC, "world", world, 0);
}

Kod hakkında çaıklamalar:

Init_hello_c başlangıç metodu adındaki hello_c kelimesi extconf.rb içinde create_makefile metoduna verdiğimiz argüman değerle aynı olmalıdır. Aksi takdirde Ruby genişleme kodunuzu bulamayacaktır. 

rb_define_module metodu çağrısı HelloC adında C fonksiyonlarımızı altında toplayacağımız bir Ruby modülü üretir. 

Son olarak rb_define_singleton_method metodu modül seviyesinde world adlı bir metod üretiyor. Direk HelloC modülüne bağlı, bu sayede HelloC.world komutu ile metodu çağırabiliyoruz. 

Derlenmiş genişlemeyi make ile elde ettikten sonra C genişlememiz içinde tanımladığımız metodu kullanabiliriz.

$ ruby extconf.rb && make
creating Makefile
compiling hello.c
linking shared-object hello_c.so

Şimdi bir irb oturumu açalım.

>> require './hello_c'
=> true
>> HelloC.world
Hello World!
=> nil

Yaşasın artık basit basit kodları C genişlemesi olarak hazırlayıp, sadece .so derlenmiş dosyasını klasörümüze ekleyip, kodlarımızı gizli yapabiliriz, aman kimse çalmasın kodlarımızı..





Ruby Struct Yapılar

Struct yapılar karmaşık verileri saklamak için kendi veri tipimizi tanımlamakta çok kullanılır. Deyim yapısı.

Structure = Struct.new :özellik_adı



-- Veriler için yeni yapılar tanımlamak

Struct sınıfı verilen bağıl özellikler ve erişim metodlarına sahip yeni sınıflar tanımlar. 

Kişi = Struct.new :adı, :soyadı

Daha sonra üretilen sınıftan yeni nesneler oluşturabiliriz.

Kişi = Struct.new :adı, :soyadı

kişi = Kişi.new 'John', 'Doe'
# => #<struct Kişi adı="John", soyadı="Doe">

kişi.adı
# => "John"

kişi.soyadı
# => "Doe"




-- Bir struct sınıfına ilave yapmak


Kişi = Struct.new :adı do
  def selamla(birisi)
    "Merhaba #{birisi}! Ben #{adı}!"
  end
end

Kişi.new('Ümit').selamla 'Hasan'
# => "Merhaba Hasan! Ben Ümit!"




-- Özelliklere erişmek


Kişi = Struct.new :adı
alice = Kişi.new 'Alice'

alice['adı']  # => "Alice"
alice[:adı]   # => "Alice"
alice[0]      # => "Alice"


İstersek iç içe yapılar oluşturarak daha karmaşık verileri paketleyebiliriz.

Adres = Struct.new :cadde, :sokak, :numara
Kişi = Struct.new :adı, :soyadı, :adres

kişi = Kişi.new "Ümit", "Kayacık", Adres.new("Anadolu cd.", "Elmas sk.", "25/A")
kişi.adres.sokak
#=> "Elmas sk."





MetaProgramlama

Metaprogramlama iki şekilde açıklanabilir.

Başka programları (veya kendisini) veri olarak kullanan bilgisayar programları. Yada çalışma zamanında yapılacak şeyleri derleme zamanında yapan programlar.

Başka bir deyişle : Metaprogramlama, çalışma zamanında hayatınızı kolaylaştırmak için kod yazan kodlar yazmaktır.




-- Oluşum değerlendirmesi kullanarak 'with' uyarlaması

Birçok programlama dilleri programcıların metod uygulanan nesneyi göstermemesini sağlayan with deyimine sahiptir. 

with deyimi Ruby'de oluşum değerlendirmesi ( instance_eval ) ile kolayca uyarlanır. 

def with(nesne, &blok)
  nesne.instance_eval &blok
end

with metodu nesneler üzerindeki metodları sorunsuz bir şekilde çalıştırmak için kullanılabilir.

with hash do
  store :key, :value
  p has_key? :key       # => true
  p values              # => [:value]
end




-- Bir nesnenin metodlarını görmek


-- -- Bir nesneyi incelemek

Bir nesnenin cevap verdiği public metodlarının listesine methods veya public_methods metodları ile erişebiliriz. 

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, :pretty_inspect,
#   :pretty_print, :pretty_print_cycle, :pretty_print_inspect,
#   :pretty_print_instance_variables, :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]

Daha da filtrelenmiş bir sonuç için tüm nesnelerde ortak olan metodları çıkartabiliriz.

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

Alternatif olarak methods ya da public_methods'a false argümanı verebiliriz.

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

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


Bir nesnenin private ve protected metodlarına private_methods ve protected_methods metodları ile erişiriz.

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.sort
# []


Aynı methods ya da public_methods gibi bu metodlara da false argümanı vererek , kalıtımla gelen metodları liste dışı bırakabiliriz.


-- -- Bir sınıf veya modülü incelemek

methods, public_methods, protected_methods, ve private_methods, metodlarına ilave olarak sınıflar ve modüller  instance_methods, public_instance_methods, protected_instance_methods, ve private_instance_methods metodları ile o sınıf yada modülden oluşturulan nesnelerin metodlarını listeler. Bunlara da yukarıda anlatıldığı gibi false argümanı vererek kalıtımla gelen metodları dışarıda bırakabiliriz.

p Foo.instance_methods.sort

p Foo.instance_methods(false)

Son olarak eğer "bu metodların isimleri neydi" diye hatırlamaya çalışırsak methods metodunu bir filtreleme ile kullanıp hatırlayabiliriz.

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]

Tabi, sınıfın da kendi metodları var çeşit çeşit.



-- Metod gönderme

Ruby'de bir nesnede metod çağırmak yerine "nesneye mesaj göndermek" tabiri çok kullanılır. Metoda tepki vermek , mesaja cevap vermek gibi de kullanılır. Bir nesneye mesaj göndermek için send metodu kullanılır, yani send metodu ile o nesnede bir metod çağrısı yapılabilir. send metodu temel sınıf olan Object sınıfının bir oluşum metodudur. Verilen ilk argüman , nesneye gönderilecek olan mesajdır - yani çağrılacak olan metod bilgisi, bu bir string veya bir symbol değer olabilir ama sembol tercih edilir. Sonraki argümanlar çağırmaya çalıştığımız metoda geçirilecek olan argümanlardır. 

class Merhaba
  def merhaba(*args)
    puts 'Merhaba ' + args.join(' ')
  end
end
m = Merhaba.new
m.send :merhaba, 'sevgili', 'okuyucular'  
#=> Merhaba sevgili okuyucular
#=> Burada :merhaba method ve gerisi o metoda verilecek argümanlar


Daha açıklayıcı bir örnek

class Hesap
  attr_accessor :isim, :email, :notlar, :adres

  def assign_values(values)
    values.each_key do |k, v|
      # send metodu
      # 'self.isim = value[k]' satırını nasıl ifade eder
      self.send("#{k}=", values[k])
    end
  end
end

user_info = {
  isim: 'Ümit',
  email: 'test@example.com',
  adres: '132 rastgele cd.',
  notlar: "sıkıcı müşteri"
}

hesap = Hesap.new
#Eğer özellik sayısı artarsa kodumuz karışmaya başlar
#--------- Kötü yöntem --------------
hesap.isim = user_info[:isim]
hesap.adres = user_info[:adres]
hesap.email = user_info[:email]
hesap.notlar = user_info[:notlar]

# --------- Meta Programlama yöntemi --------------
hesap.assign_values(user_info) # tek satırla birçok özellik değeri

puts hesap.inspect


Not: send artık pek tavsiye edilmiyor private metodları da çağıran __send__ (ki aslında bu send metodunun orjinalidir, üzerine yazılsa dahi bu şekil çağrılırsa orjinali gelir) metodu ya da (tavsiye edilen) public_send metodu kullanılıyor.



-- Metodları dinamik tanımlamak

Ruby'de programımızın yapısını çalışma zamanında da değiştirebiliriz. Bunu yapmanın yöntemlerinden biri Ruby'nin bir metodu bulamayınca otomatik çağırdığı method_missing metodunu kullanmak. Rails'çiler bunu çok yapar. 

Diyelim bir sayının başka bir sayıdan büyük olmasını 777.büyük_mü_123_ten? şeklinde bir metodla test etmek istiyoruz. 

# Numeric sınıfını aç
class Numeric
  # `method_missing` üzerine yaz
  def method_missing(method_name,*args)
    # method_name istediğimiz deyime benziyor mu?
    if method_name.to_s.match /^büyük_mü_(\d+)ten\?$/
      # metod ismindeki sayıyı çek
      diğer_sayı = $1.to_i # eşleşen ilk grup
      # diğer sayı ile karşılaştırma sonucunu dön
      self > diğer_sayı
    else
      # eğer metod adı istediğimizle uyuşmuyorsa
      # `method_missing`in önceki tanımının işlemesini sağla
      super
    end
  end
end


method_missing işlerken dikkat edilmesi gereken bir başka konu respond_to? metodunu da elden geçirmemiz gerekir. 

  def respond_to?(method_name, include_all = false)
    method_name.to_s.match(/^büyük_mü_(\d+)ten\?$/) || super
  end


Bunu unutursak 777.büyük_mü_123_ten? çalışır ama 777.respond_to?(:büyük_mü_123_ten?) false değer döner.

Bir de ten yerine den ya da dan da olabilir sayıya göre. Programın son hali.

class Numeric
  # `method_missing` üzerine yaz
  def method_missing(method_name,*args)
    # method_name istediğimiz deyime benziyor mu?
    if method_name.to_s.match /^büyük_mü_(\d+)_(ten|den|dan)\?$/
      # metod ismindeki sayıyı çek
      diğer_sayı = $1.to_i # eşleşen ilk grup
      # diğer sayı ile karşılaştırma sonucunu dön
      self > diğer_sayı
    else
      # eğer metod adı istediğimizle uyuşmuyorsa
      # `method_missing`in önceki tanımının işlemesini sağla
      super
    end
  end

  def respond_to?(method_name, include_all = false)
    method_name.to_s.match(/^büyük_mü_(\d+)_(ten|den|dan)\?$/) || super
  end
end

p 777.büyük_mü_123_ten?   #true
p 6.büyük_mü_7_den?       #false
p 77.büyük_mü_66_dan?     #true
p 600.respond_to?(:büyük_mü_1000_den?)
#<MatchData "büyük_mü_1000_den?" 1:"1000" 2:"den">



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

Bir nesnenin oluşum değişkenlerini instance_variables, instance_variable_defined?, ve instance_variable_get metodları ile sorgulamak mümkündür. Oluşum değişkenlerini 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)    #=> 17
p f.bar                               #=> 17
f.remove_instance_variable(:@bar)     #=> 17
p f.bar                               #=> nil
p f.instance_variables                #=> []


Oluşum değişken isimleri @ karakteri ile birlikte yazılmalıdır, unutursanız hata verir.

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




-- Oluşum nesnesinde kod çalıştırmak

Bir nesnenin instance_eval metodu , verilen kod bloğunu o nesne kapsamında çalıştırır. 

nesne = Object.new

nesne.instance_eval do
  @değişken = :değer
end

p nesne.instance_variable_get :@değişken  # => :değer


instance_eval metodu kod bloğunu çalıştırırken self değerini nesne değerine eşitler.

p nesne.instance_eval { self == nesne }  # => true


Metodun alıcı nesnesi bloğa yegane argüman olarak gönderilir. 

p nesne.instance_eval { |argument| argument == nesne }  # => true


Benzer metod olan instance_exec bu noktada farklı davranır. O, bloğa kendi argümanlarını gönderir.

nesne.instance_exec :@değişken do |isim|
  p instance_variable_get isim  # => :değer
end




-- Global ve yerel değişkenleri okumak

Kernel modülü global ve yerel değişkenleri için global_variables ve local_variables metodları içerir.

kediler  = 42
$demo = "çalışıyor"
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
#=> [:kediler]


Oluşum değişkenlerinden farklı olarak yerel ve global değişkenleri okumaki değiştirmek veya yok etmek için metodlar yoktur. Direk kendilerini kullanırsınız. Bununla beraber eğer bir değişkene ismiyle ulaşıp bir şeyler yapmak için eval metodundan yararlanılabilir.

var = "$demo"
p eval(var)           #=> "çalışıyor"
eval("#{var} = 17")
p $demo             #=> 17


eval metoduna verilen string içindeki kod çalıştırılır. 

>> eval "2+5"
=> 7
>> a = 3
>> eval "a * 2"
=> 6


Default olarak eval metodu değişkenlere bulunulan kapsamda erişir. Başka bir kapsamdaki değişkene erişmek için o kapsamın binding değeri ikinci argümanda kullanılabilir.

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

def test_1
  foo = :dışarda1
  p local_variable_get("foo")
  # kapsam verilmeden çağrılmış
end

def test_2
  foo = :dışarda2
  p local_variable_get("foo", binding)
  # bulunulan kapsam ile çağrılmış
end
 
test_1 #=> :içerde
test_2 #=> :dışarda2


Ruby dökümanındaki örnek de ilginç.

def get_binding(str)
  return binding
end
str = "hello"
eval "str + ' Fred'"                      #=> "hello Fred"
eval "str + ' Fred'", get_binding("bye")  #=> "bye Fred"


Anlamak için get_binding metod tanımında parametre adını değiştirirsek verilen hatadan görebiliriz. İkinci eval satırı "bye" string'i kapsamında str değişkeni arıyor ve metoda gönderilen "bye" değerini kullanıyor.




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

Sınıfların ve modüllerin de nesneler gibi değişkenlerine erişme metodları vardır.

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


Oluşum değişkenlerinde olduğu gibi sınıf değişkenlerinde de isimi @@ olamadan yazarsak hata alırız.

p Bar.class_variable_defined?( :instances )
#=> NameError: `instances' is not allowed as a class variable name




-- Metodları string'den dinamik üretmek

Ruby define_method metodunu modüller ve sınıflarda metod tanımlamak için private olarak bulundurur. Ancak metodun gövdesi ya bir Proc nesnesi ya da başka bir metod olmalıdır. Bir string'den metod üretmenin yöntemi eval kullanarak string'den bir Proc üretmek olabilir. 

xml = <<ENDXML
<metodlar>
  <metod ismi="go">puts "Gidiyorum!"</metod>
  <metod ismi="stop">7*6</metod>
</metodlar>
ENDXML

class Foo
  def self.add_method(isim, kod)
    gövde = eval( "Proc.new{ #{kod} }" )
    define_method(isim, gövde)
  end
end

require 'nokogiri' # gem install nokogiri
doc = Nokogiri.XML(xml)
doc.xpath('//metod').each do |meth|
  Foo.add_method( meth['ismi'], meth.text )
end
f = Foo.new
p Foo.instance_methods(false)  #=> [:go, :stop]
p f.public_methods(false)      #=> [:go, :stop]
f.go                           #=> "Gidiyorum!"
p f.stop                       #=> 42


İşte böyle, meta programlamada biraz yapılabileceklerin sınırları zorlanıyor. 



-- Oluşum nesnelerinde metod tanımlamak

Daha önce de gördük Ruby'de bir sınıfın oluşum nesnesine sadece ona ait olan bir metod tanımlayabiliyoruz. Böylece sınıftan üretilen diğer nesneleri etkilemeden sadece bir nesnenin davranışını değiştirebiliriz. 

class Örnek
  def metod1(foo)
    puts foo
  end
end

# exp nesnesinde metod2'yi tanımlar
exp = Örnek.new
exp.define_singleton_method(:metod2) {puts "Metod2"}

# parametresi olan metod tanımlama
exp.define_singleton_method(:metod3) {|isim| puts isim}

exp.metod1 "metod1"   #=> metod1
exp.metod2            #=> Metod2
exp.metod3 "Ümit"     #=> Ümit

exp2 = Örnek.new
exp2.metod3 "Hasan" # (NoMethodError)

Bir diğer yöntem direk def bloğunda nesne adını kullanarak.

def exp.metod3(isim)
  puts isim
end





Dinamik Kod Çalıştırma













Hiç yorum yok:

Yorum Gönder