https://ujk-ujk.blogspot.com/2025/03/ruby-temelleri-8.html
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