Burak Selim Senyurt

Matematik Mühendisi Bir .Net,Ruby ve Python Severin Maceraları

Ruby Kod Parçacıkları 29 - Kod Yazan Kodlar

Merhaba Arkadaşlar,

Ruby programlama dilinin öne çıkan özellikleri arasında dinamiklik ve meta programlama yetenekleri de yer almaktadır. Aslında işin özeti kod yazan kodlardır diyebiliriz. Ruby yorumlamalı bir dildir ve doğal olarak her şey çalışma zamanında icra edilmektedir. Ancak bazı senaryolarda metinsel olarak gelen bir içeriğin kod parçası şeklinde değerlendirilmesi ve çalışma zamanında yürütülmesi istenebilir. Hatta çalışan kod üzerinde değişiklikler yapılabilmesi de belli ölçülerde mümkündür. Konu pek tabii benim burada anlatacağım kadar basit değil. Örneğin Amazon'da bu konu üzerine yazılmış kitaplar bulabilirsiniz. Bu yazımızda kodu çalışma zamanında üretmek veya çalışan kodu manipule etmek için yapılabileceklerden bir kaçına değinmeye çalışacağız.

Bazı durumlarda Meta-programlama ile Reflection birbirleri ile karışıtırılır. Normal de Reflection Meta-programlama'nın bir alt dalı olarak karşımıza çıkar ve özellikle yorumlamalı betik dillerde program kodu hakkında çalışma zamanına bilgi sağlamak amacıyla kullanılır. Bir sınıfın özellik adlarını yakalamak gibi. Oysa ki meta-programlama ile çalışan kodun çalışma zamanında değiştirilmesi veya çalışabilecek metinsel kod parçalarının yine çalışma zamanında ayağa kaldırılıp yürütülmesi gibi işlemler kastedilir. Bu açıdan bakıldığında meta-programlama'nın yer bulduğu en önemli alan Domain Specific Language yazılmasıdır.

Eval ile Çalışma Zamanında Kod İşletmek

İlk örneğimizde eval fonksiyonelliğini ele alarak metinsel bir kod parçasını nasıl çalıştırabileceğimizi anlamaya çalışacağız.

puts 'insert a simple code'
code=gets
eval code

someCodes="puts 'whats your name?'
        name=gets
        puts name.upcase!()
"
eval someCodes

İlk olarak çalışma zamanı çıktısına bir bakalım dilerseniz.

Sizi bilemem ama ben bu kod parçasına baktığımda epeyce etkileniyorum. İlk olarak kullanıcıdan ekrana bir kod parçası girmesini istiyoruz. Tabii örnekte işletilebilir bir kod parçası eklediğimizi itiraf etmeliyim(Hatalı bir kod parçasının nasıl tepki vereceğini incelemek ise size bir ödev olsun) Sonrasında ise birden fazla satıra yayılan bir kod parçası söz konusu. Önemli olan nokta code ve someCodes değişkenlerinin eval ile birlikte kullanılması. Yani çalışan bir kodun içerisinde başka bir ruby kodunun çalıştırılmasını sağlamış olduk. eval, parametre olarak gelen ifadenin değerlendirilmesini yapıp yorumlanacak şekilde ruby ortamına aktarmakla görevli. Bu arada eval, RubyKernel API'si içerisinde yer alan instance metodlarından birisidir.

Binding

Bir diğer Kernel metodu ise binding'dir. eval fonksiyonu ile birlikte kullanılır. Temel olarak bir metod ve değişkenlerini program ortamı içerisinde başka bir noktaya bağlayabilmemize olanak sunar. Konuyu açıklamak için aşağıdaki kod parçasını ele alalım.

def doSomething
  puts "in doSomething"
  return binding
end
bind=doSomething{3.times{puts "arigatou"}}
eval "yield",bind

ve çalışma zamanı görüntüsü.

doSomething metodu içerisinde binding kullanılmıştır. Buna göre çalışma zamanında doSomething metodu ve parametrik yapısı yaklanarak eval ifadesi üzerinden kendisine kod parçası gönderilebilir. Şimdi konuyu dikkatlice inceleyelim. Çalışma zamanında ekrana ilk olarak "in doSomething" yazar. Sonrasında ise 3 kez "arigatou". Bunu 3.times{puts "arigatou"} ifadesinin gerçekleştirdiği aşikardır. Bu ifadenin doSomething metodu içerisine gönderilmesi içinse eval fonksiyonuna iki parametre geçilmiştir. İlki yield anahtar kelimesi, ikincisi ise metodun bağlandığı değişken olan bind(Açıkçası binding konusu bana biraz karışık geldi. Daha iyi bir şekilde öğrenmek için uğraşıyorum)

Bir Sabitin Çalışma Zamanında Yakalanması

Pek tabii dinamiklik ve meta-programlama söz konusu ise ortada reflection gibi konularda yer alacaktır. Örneğin çalışma zamanında bir değişmezi(Constant) yakalamak için const_get metodundan yararlanılabilir(eval'e göre daha performanslı oldu��u ifade edilmektedir) Aşağıdaki kod parçasını ele alalım.

eValue="E"
piValue="PI"
result1=Math.const_get(eValue)*10
result2=Math.const_get(piValue)*10*10
puts result1,result2

pi=eval "Math::PI"
puts pir

Bu kod parçasında Math sınıfında yer alan PI ve E değişmezlerinin çalışma zamanında bir string üzerinden yorumlanması örneklenmektedir. eValue ve piValue değişkenleri dikkat edileceği üzere string veri türündedir. Math sınıfı üzerinden const_get metodu kullanılarak bu iki değişmezin değeri yakalanabilir. Dolayısıyla kendi tanımladığımız bir sabitin değerini de bu şekilde çalışma zamanında yakalamamız mümkün. İkinci kod parçasında sabit değerinin eval ile yorumlanması örneklenmiştir. Çalışma zamanına ait çıktımız aşağıdaki gibi olacaktır.

Bir Sınıfı Adından Örneklemek 

Dilersek bir sınıfı yine metin olarak gelen adından örnekleyebiliriz. .Net tarafında da reflection teknikleri ile yapabildiğimiz bir operasyon olduğunu biliyoruz. Aslında bir sınıfın çalışma zamanında örneklenmesi olarak ifade edebileceğimiz bir durum(Tabii Ruby'de her şey çalışma zamanında gerçekleşiyor bunu da unutmamak lazım) Konuyu aşağıdaki örnekle anlamaya çalışalım.

module Game
  class Player
    attr_accessor :name,:point
    def initialize(name,point)
      @name,@point=name,point
    end
    def to_s
      "#{@name}-(#{@point})"
    end
  end
end

# sinif module icinde oldugundan :: notasyonu kullaniliyor
obj=Object.const_get("Game::Player") #sinif adini alip
o=obj.new("burk",100) #ornekliyoruz
puts o.to_s #ve icindeki bir metodu kullaniyoruz

Game modülü içerisinde örnek olarak ele aldığımız Player isimli bir sınıf bulunuyor. Sınıfa sembolik olarak bir kaç nitelik ve ezilmiş to_s metodunu ekledik. Bizi ilgilendiren kısım ise Object sınıfı üzerinden çağırdığımız const_get fonksiyonu. Bu, parametre olarak "Game::Player" şeklinde bir metin almakta. Player sınıfı bir modül içerisine yer aldığından :: notasyonuna başvuruyoruz. Bu satır ile Player tipinden bir örnek üretiliyor ve obj isimli değişkene aktarılıyor. Player.new gibi bir oluşumdan farklı bir şey yaptığımızı fark ediyorsunuz değil mi? Nitekim obj.new ile Player nesnesi oluşturmaktayız evet ama nesne adı string olarak gelmekte. Hatta yapıcı metoda parametrelerini gönderip to_s metodunu da kullanıyoruz. İşte çalışma zamanı çıktıları.

define_method ile Çalışma Zamanında Metod Oluşturmak

Ruby dilini öğrenmeye çalışırken özellikle dinamik olması ve meta-programlama yetenekleri içermesi nedeniyle anlamakta zorlandığım pek çok kısım oluyor. define_method'da bunlardan birisi. Temel olarak çalışma zamanında metod üretebilmemize izin veren bir fonksiyonellik olarak düşünebiliriz. Konuyu öğrenirken ki tek sıkıntım işe yaramayacak olsa da nasıl uygulandığını gösteren bir örnek bulmak oldu. İlk olarak aşağıdaki gibi bir sınıfımız olduğunu düşünelim.

class GameZone
  def title=(zone_name)
    @title=zone_name
  end
  def title
    @title
  end
  def capacity=(player_count)
    @capacity=player_count
  end
  def capacity
    @capacity
  end
  def color=(color)
    @color=color
  end
  def color
    @color
  end
end
rogue_one=GameZone.new
rogue_one.title="Rogue One"
rogue_one.capacity=48
rogue_one.color="black"
puts rogue_one.title,rogue_one.capacity,rogue_one.color

Bu kod parçasında GameZone isimli bir sınıf ve bir kaç nitelik metodu tanımı görüyoruz. Aslında title,capacity ve color nitelikleri için attr_accessor da kullanılabilir ki genelde öyle yapılıyor ancak amacımız bu metod ihtiyaçlarının çalışma zamanında nasıl üretilebileceğini görmek. Aşağıdaki kod parçasını inceleyince ne demek istediğimi daha iyi anlayacaksınız.

class GameZoneV2
   PROPERTIES=["title","capacity","color"]
   
   PROPERTIES.each{|p|
     define_method("#{p}="){|i|
       instance_variable_set("@#{p}",i)
     }
     
     define_method("#{p}"){
       instance_variable_get("@#{p}")
     }
   }
end
zone_gold=GameZoneV2.new
zone_gold.title="Gold zone"
zone_gold.capacity=34
zone_gold.color="Gold"
puts zone_gold.title,zone_gold.capacity,zone_gold.color

GameZoneV2 içerisinde yine sihirli bir şeyler var. PROPERTIES isimli dizi içerisindeki her bir eleman için getter ve setter metodları define_method yardımıyla çalışma zamanında üretilmekte. each bloğu içerisinde her bir p değişkeni(PROPERTIES elemanı) için iki define_metod çağrısı söz konusu. İlkinde = ile biten setter metod oluşturuluyor ki burada instance_variable_set ile atama bildirimi de yapılmakta. İkinci define_method ile de getter fonksiyonu tanımlamakta. Kodun ilerleyen kısımlarında zone_gold isimli nesne örneği üzerinden title, capacity ve color niteliklerinin kullanılabildiğine şahit oluyoruz. İşte çalışma zamanı sonuçları.

Demek ki çalışma zamanında gelecek bir takım parametrelere göre sınıflara farklı operasyonları eklememiz mümkün. Dikkat edin çalışma zamanında diyorum.

self.class.send Kullanarak metod işletmek

Olayı biraz daha ilginç hale getirmeye ne dersiniz? Mesela sınıfa dahil etmek istediğimiz getter ve setter metodlarının sahibi olacak nitelikleri de dışarıdan verebiliyor olalım. Aşağıdaki kor parçasını bu anlamda ele alabiliriz.

class Person
  def createGetterSetter(*args)
    Array(args).each{|attr|
      self.class.send(:define_method,"#{attr}="){|v|
        instance_variable_set("@#{attr}", v)
      }
      self.class.send(:define_method,"#{attr}") {
        instance_variable_get("@#{attr}")
      }
    }
  end
end
logan=Person.new
logan.createGetterSetter("name","salary")
logan.name="burki"
logan.salary=1250
puts "#{logan.name}-(#{logan.salary})"

Person sınıfı içerisinde yer alan createGetterSetter metodu değişken sayıda parametre alabilmektedir. İçinde yer alan each bloğunda her bir argüman için birer getter ve setter metodu tanımlanması sağlanmaktadır. Burada dikkat edilmesi gereken nokta define_method'un kullanım şeklidir. define_method private tanımlanmış bir sınıf fonksiyonudur. Bunu Player örneği üzerinden çağırmak için self.class.send şeklinde bir yol izlenmelidir. Kodun ilerleyen kısımlarında createGetterSetter metoduna örnek iki eleman yollanmış ve kullanılmıştır.

Aynı örnekte işi biraz daha ileriye götürebiliriz. Zaten attr_accessor bir nitelik için gerekli getter ve setter operasyonlarını hazır olarak sunmaktadır. Peki aynı örnekteki nitelikleri attr_accessor ile tanımlayabilir miyiz? Tabii çalışma zamanında. Aşağıdaki kod parçası işimizi görecektir.

class Person
  def createAttr(*args)
    Array(args).each{|attr|
      self.class.send(:attr_accessor,"#{attr}")
    }
  end
end
logan=Person.new
logan.createAttr(:name,:salary)
logan.name="burki"
logan.salary=1250
puts "#{logan.name}-(#{logan.salary})"

Yine self.class.send metodunun kullanıldığına dikkat edelim. Bunun dışında createAttr metoduna :name ve :salary isimli iki symbol göndermekteyiz.

Görüldüğü gibi metinsel olarak gelen bir kod parçasını çalışma zamanında işletmemiz mümkün. Bir sınıfa çalışma zamanında gelecek bilgiler ve yönergeler doğrultusunda yeni fonksiyonellikler katmamız da söz konusu. Bir sınıfı adından yola çıkarak örneklememiz veya içerisindeki değişmez değeri yakalamamız da mümkün. Daha yapılabilecek pek çok şey olduğu da ortada. Bunları ilerleyen bölümlerimizde incelemeye gayret edeceğim. Böylece geldik bir kod parçasının daha sonuna. Tekrardan görüşünceye dek hepinize mutlu günler dilerim.

Loading