Bir Java’cının Gözünden Ruby

Son zamanlarda en çok merak edip, öğrenmek istediğim diller arasında geliyor Ruby. Ruby herkesin dilinden düşürmediği, dinamik veri tipli ve her şeyin istisnasız nesne olduğu bir programlama dili. 1993 yılında Yukihiro Matsumoto tarafından geliştirilmiş. 2006 senesinde Ruby on Rails çatısının oluşturulmasıyla popüler bir web programcılığı dili olmuş.

Bu yazımda bir Java programcısının, yani benim Ruby dilini nasıl algıladığını, bu dilin kendisini nasıl hissettirdiğini sizlere aktarmak istiyorum.

Java’da her zaman şöyle bir şey yapmak istemişimdir:

throw new Exception() if(abc....);

Böyle bir yapı Java’da mümkün değil. Onun yerine her zaman şöyle bir şey yazmak zorundasınız:

if(abc...)
{
    throw new Exception();
}

Tek bir satır ile ifade etmek istediğim bir düşünce için Java’da dört satır kod yazmam gerekiyor. Java’nın en büyük sorunlarından birisi bu. Bu zamanla Java’da yazılan programların okunamaz hale gelmesine sebep oluyor. İşin içine birde hata yönetimi (Exception Handling) girdi mi, kodu anlayana aşk olsun. Ruby’de dikkatimi çeken ve Java’da hasretini çektiğim if ve unless yapıları oldu. Ruby’de şöyle bir şey mümkün:

puts 'hello world' if x==4
puts 'hello world' unless x==4
unless x == 4
    puts 'hello world'
end
order.calculate_tax unless order.nil?

Belli bir düşünceyi Ruby’de olduğu gibi tek bir satır olarak ifade etmenin kodun okunabilirliği açısından çok büyük getirisi var. Bu satırı bir blok halinde yazmak elbette mümkün. Lakin bu ifade edilmek istenen düsüncenin kod arasında kaybolma rizikosunu artırıyor. Burada Ruby’nin takip ettigi filozofinin “ne kadar az kod, o kadar çok ifade edilmek istenen düsüncenin ön plana çıkması” olduğu belli oluyor.

Aynı sadelik Ruby’nin while ve until döngüleri için de geçerli:

x = x+1 while x < 10
&#91;/source&#93;

&#91;source language='ruby'&#93;
x = x-1 until x == 10
&#91;/source&#93;

Ruby'de her şey nesne:

&#91;source language='ruby'&#93;
>>  4.class
==> Fixnum
>>  4+4
==> 8
>>  4.methods
==> ["inspect", "%", "<<", "singleton_method_added", "numerator","*","+","to_i"......&#93;
>>  true.class
==> TrueClass
>>  false.class
==> FalseClass

Java’da 4 rakamı int primitif tipine sahip iken, Ruby’de Fixnum tipinde bir nesne. 4.methods bu nesnenin sahip olduğu metotları sıralıyor. Ruby safkan bir nesneye yönelik programlama dili.

Ruby’de duck typing ismi verilen ilginç bir konsept uygulanmış. Bunun ne olduğunu açıklamadan önce bir Java örneği sunmak istiyorum.

public void printFileName(File file)
{
    if(file.exists())...
}

printFileName() ismini taşıyan metodun file isminde bir parametresi bulunuyor. Metot gövdesinde bu parametrenin sahip olduğu sınıf tipine göre değişik metotlar koşturabilirim. Bunlardan bir tanesi exists() metodu. Bu metodu kullanabilmem için file ismindeki metot parametresinin File sınıfından türetilmiş olması gerekiyor, aksi taktirde exists() metodunu kullanamam. Java’da bir nesnenin sahip olduğu tip o nesnenin sınıfı tarafından belirleniyor. File tipinde olan bir değişken sadece File sınıfının metotlarını kullanabilir. Ruby’de durum farklı. Aradaki farkı görmek için bir Ruby örneğini inceleyelim:

def copy_file(obj, dest_path)
    src_path = obj.get_path
    puts "Copying file \"#{src_path}\" to \"#{dest_path}\"."
end

copy_file() metodunun obj ve dest_path isminde iki tane parametresi bulunuyor. Bu iki parametrenin hangi tipte olduğu belli değil. Tipini, yani sınıfını tanımadığım bir nesnenin nasıl olurda obj.get_path() şeklinde bir metodunu koşturubilirim? obj nesnesinin ait olduğu sınıfta get_path() isminde bir metod olduğunu nereden biliyorum? Bilmiyorum, bilmek te zorunda değilim.

Ruby’de her şey nesne. Durum böyle olunca Ruby’nin kontrol etmesi gereken tek şey, kullanmak istediğim metodun nesnede var olup, olmadığı. Eğer kullandığım metodu Ruby nesnede bulamassa NoMethodError hatası oluşturur. Bunu da yakalayıp, gerekli işlemi yapmak zor değil. Buradan çıkardığımız sonuç şu: Ruby nesneleri üzerinde istediğim herhangi bir metodu koşturabilirim. Eğer nesnenin böyle bir metodu varsa, nesne istediğim şekilde davranış sergileyecektir. Aksi taktirde NoMethodError hatası oluşacaktır. Bu Ruby’de nesne tipinin sahip olunan sınıfa göre değil, nesnenin ihtiva ettiği metotlara göre belirlendiği anlamına geliyor.

Ruby’de Java ya da C# da olduğu gibi bir sınıf tanımlamak zorunda kalmadan şu şekilde bir metot tanımlanabiliyor:

def  bugunHavaSicakMi
    true
end

Ruby’de her fonksiyon bir değer geri veriyor. Eğer metodun son satırında return komutu kullanılmadı ise, o zaman son satırda yer alan değer geri veriliyor. Yukarıda yer alan örnekte geri verilen deger true olacaktır.

Bir array şu şekilde tanımlanabiliyor:

>>  hayvanlar = ['Kedi', 'Fare', 'Yilan']
==> ["Kedi", "Fare", "Yilan"]
>>  puts hayvanlar
Kedi
Fare
Yilan
>>  hayvanlar[0]
==> Kedi
>>  hayvanlar[10]
==> nil
>>  hayvanlar[-1]
==> "Yilan"
>>  hayvanlar[0..1]
==> ["Kedi", "Fare"]

Var olmayan bir array elementine erişmek istediğimizde Ruby nil değerini geri veriyor. Java’da bu IndexOutOfBoundException hatası olurdu. Index olarak eksi değerler kullanıldığında Ruby arrayi sondan başa doğru görmeye başlıyor. Örneğin hayvanlar[-1] “Yilan”, hayvanlar[-2] “Fare” değerini geri verir.

Ruby ile ismi olmayan anonim fonksiyonlar tanımlanabiliyor. Bu anonim fonksiyon başka metotlara parametre olarak ta vermek mümkün. Aşağıdaki örnekte hello world ekranda üç sefer görüntülenir. Küme parantezi içinde yer alan kod aninim fonksiyondur.

3.times {puts 'hello world'}

Anonim fonksiyonları parametrelendirmek mümkündür. Aşağıdaki örnekte each bir for döngüsü oluşturup, hayvanlar isimli listenin her elementini döngü içinde anonim fonksiyona parametre olarak vermektedir. Tek satırlık kod ile tüm listenin içeriğini ekranda görüntülemek münkündür.

hayvanlar = ['Kedi', 'Fare', 'Yilan'] 
hayvanlar.each { |a|  puts a}

Java’da anonim fonksiyon tanımlamak için bir anonim class implementasyonu oluşturmak gerekiyor. Bir listedeki değerleri anonim bir fonksiyon tarafından ekranda görüntülemek için aşağıdaki şekilde Java kodu yazardık:

package xxx;

import java.util.ArrayList;
import java.util.List;

public class Test {

	final static List<String> animals = new ArrayList<String>() {
		{
			add("dog");
			add("cat");
		}
	};

	private static abstract class AnimalLister {
		abstract void show(String animal);
	}

	public static void main(String[] args) {

		each(new AnimalLister() {
			@Override
			void show(String animal) {
				System.out.println(animal);
			}
		});
	}

	private static void each(AnimalLister animalLister) {

		for (String animal : animals) {
			animalLister.show(animal);
		}
	}
}

Java’da bir sınıfa yeni bir metot eklemek için ya o sınıfı yeniden derlemek, ya AOP kullanmak ya da sınıfı bytecode seviyesinde manipüle etmek gerekiyor. Ruby’de mevcut bir sınıfa yeni bir metot eklemek çocuk oyuncağı:

class Fixnum
  def my_times
    i=self
    while i > 0
      i = i-1
      yield
    end
  end
end

3.my_times {puts 'hello world'}

Yukarıda yer alan Ruby örneğinde Fixnum isimli sınıfa my_times isminde yeni bir metot ekledik. Oluşturduğumuz bu yeni metodu 3.my_times şeklinde kullanabiliriz. JDK içindeki bir sınıfa doğrudan yeni bir metot eklemek istediginizi düşünün. Ruby’de mevcut her sınıfa yeni bir metot eklemek mümkün.

Java gibi nesneye yönelik programlama yapılan dillerde ortak kullanılan metot ve değişkenler kalıtım yolu ile altsınıflara verilir. Ruby’de bu mekanizma modüller aracılığı ile işlemektedir. Bir modül içinde fonksiyonlar ve değişkenler yer alır. Herhangi bir Ruby sınıfı tanımlı bir modülü kendi bünyesine kattığında, modül içinde yer alan tüm metot ve değişkenler sınıf tarafından kullanılabilir.

Aşağıda yer alan Ruby örneğinde ToFile isminde bir modül yer alıyor. to_f metodu to_s metodundan gelen değeri bir dosyaya ekliyor. to_s metodu modül bünyesinde tanımlı değil. Bu metodun modülü bünyesine katan sınıf tarafından implemente edilmiş olması gerekiyor. Ruby daha önce bahsettiğim duck typing yönetimi ile nesnenin to_s metoduna sahip olup, olmadığını kontrol ediyor.

Person sınıfı include ToFile ile modülü bünyesine katıyor ve to_f metodunu kendi bünyesinde tanımlıymış gibi kullanabilir hale geliyor.

module ToFile
  def filename
    "object_#(self.object_id).txt"
  end
  
  def to_f
    File.open(filename, 'w') {|f| f.write(to_s)}
  end
end


class Person
  include ToFile
  attr_accessor :name
  
  def initialize(name)
    @name = name
  end
  
  def to_s
    name
  end
end

Person.new('özcan acar').to_f

Bu örneği Java’da aşağıdaki şekilde yazabilirdik. Java’da sadece tanımlı olan metotlar kullanılabilir. Ruby ToFile modülü örneğinde yer alan to_s metodunu kullanabilmek için bu metodun bir sınıf bünyesinde tanımlı olması gerekir. Aşağıda yer alan örnekte To_S isminde bir interface sınıf tanımlayarak, to_s metodunu bu sınıfa ekliyoruz. Soyut olan ToFile sınıfı bu interface sınıfı implemente ettiği için to_s metodunu tanır hale geliyor. Person sınıfı ToFile sınıfını genişlettiği için to_s metodunu implemente etmek zorunda. Person sınıfı ToFile sınıfını genişlettiği için to_f metoduna sahip oluyor. Person nesnesi tarafından to_f metodu koşturulduğunda, to_f metodu person nesnesi bünyesinde yer alan to_s implementasyona erişip, gerekli değeri edinebiliyor.

Ruby ile Java arasındaki en büyük fark, Java’nın kullanılan metotların hangi sınıflara ait olduklarından haberdar olması zorunluluğu. Ruby duck typing mekanizması ile bu zorunluluğu ortadan kaldırıyor.

public interface To_S {

	String to_s();
}
public abstract class ToFile implements To_S {

	public String filename() {
		return this.getClass().getSimpleName();
	}

	public void to_f() {
		FileUtils.writeStringToFile(new File("xxx"), to_s());
	}
}
public class Person extends ToFile{

	private String name;

	public Person(String name) {
		this.name = name;
	}

	@Override
	public String to_s() {
		return this.name;
	}

	public static void main(String[] args) {
		new Person("özcan acar").to_f();
	}
}

Bu kısa tanıtım umarım Ruby diline ilginizi çekebilmiştir. Ruby dilinin ifade edilmek istenen düşüncenin çok az satır ile, düşüncenin kod kargaşası arasında kaybolmadan implemente edilmesini mümkün kılması beni etkileyen en önemli özelliği oldu. Benim için ön planda olan kodun nasıl yazıldığı değil, düşüncenin kod olarak nasıl ifade edildiğidir. Programcılıkta zamanla ilgi alanı problem çözme, algoritma kullanımı vs. gibi konulardan, düşüncelerin sade ve çok kısa kod parçası olarak kodlanabilmesi yönüne kaymaktadır. Bu en azından benim için geçerli olan bir durumdur. Bu sebepten dolayı Ruby gibi düşüncelerin sade ve yüksek bir soyutluk seviyesinde ifade edilmesine izin veren dilleri tercih ederim.


EOF (End Of Fun)
Özcan Acar

Bir Java’cının Gözünden Ruby” hakkında 7 yorum

  1. Halil AYYILDIZ

    Hocam teşekkkürler…
    Yalnız bu yazının bir de Scala versiyonunu görmek isteriz..

  2. aydintd

    Merhaba

    Yazınız çok yararlı! Özellikle Ruby dilini bir Java geliştiricisinden okumak güzeldi.

    Bir düzeltme yapmak isterim müsadenizle :

    nil değeri için yazınızda nill olarak bahsetmişsiniz. Ancak Ruby dilinde NilClass nil sınıfı olup, nesnesinin adı nil dir. Yani tek “l” harfi ile.

  3. Mustafa

    Ruby öğrenmek istediğim bir dil, bir kitap yazsanız ya da e-kitap sunulsa çok güzel olur. Bu arada yazı çok iyi bence, ellerinize sağlık.

Yorumlar kapalı.