Fonksiyonel Programlama 101

Mert Elifoğlu
5 min readJun 13, 2020

--

Fonksiyonel programlama paradigmasını tam anlamıyla açıklayan bir iki cümlelik tanım maalesef bende yok, ancak bir kod parçacığının fonksiyonel olup olmadığına sanırım karar verebilir durumdayım. Buradan yola çıkıp, benim için tanımlaması çok kolay olmayan bu paradigmayı kod örnekleri üzerinden, hatta çoğunluğun haşır neşir olduğu Java üzerinden anlatmanın iyi olabileceğini düşündüm. Bu yazıda fonksiyonel programlamanın ne olduğundan ziyade bize ne kattığı, neden kullanılması gerektiği gibi sorulara cevap bulabilirsiniz.

Öğretmenin öğrencilerine ödevler verip notlandırma yapabildiği bir sistem tasarlıyor olalım. Öğrenci sınıfımız oldukça basit:

public class Student {

public Integer id;
public String name;

public Student(int id, String name) {
this.id = id;
this.name = name;
}

public void assignHomework() {
System.out.println("A new homework assigned to " + name);
}
}

Sonrasında, öğrenci bilgilerine erişebileceğimiz bir map oluşturalım:

Map<Integer, Student> studentMap = new HashMap<>();
Student ali = new Student(1, "ali");
Student veli = new Student(2, "veli");
studentMap.put(ali.id, ali);
studentMap.put(veli.id, veli);

Öğretmen, öğrenciye arayüz üzerinden öğrencinin numarasını girerek yeni ödev atayabiliyor olsun. Bu özelliği şu kod ile kolayca sağlayabiliriz:

Student selected = studentMap.get(1);
selected.assignHomework();

Öğrenci numarası 1 olan Ali’ye yeni ödev ataması yapıldı, hiçbir sorun yok. Şimdi ise öğretmenin Ali’nin öğrenci numarasını yanlış girdiği durumu ele alalım:

Student selected = studentMap.get(11);
selected.assignHomework(); //throws NullPointerException

Map’imizde key’i 11 olan bir entry olmadığından get() metodu null döndü, null olan selected objesinin assignHomework metodunu çağırmaya çalıştığımız için de NullPointerException ile karşılaştık.

Bu durumu kod yazarken genelde şu şekilde handle ediyoruz:

Student selected = studentMap.get(11);
if (selected != null) {
selected.assignHomework();
}

Bu noktada, “burada null kontrolü yapmam gerek” düşüncesi ile, programın ana akışını bir kenara bırakarak dikkatimizi istenmeyen bazı durumlarla baş etmeye vermiş oluyoruz. Bu istenmeyen durumun esas kaynağı ise Map’imizin get() metodunun bize null döndürebilecek olması, ve kodlama esnasında bu null değerini handle etmemiz için hiçbir uyaranın olmaması. Böyle bir yaklaşımla, objelerin null olabilme durumunu sürekli dikkate almalı ve kesinlikle handle etmeliyiz. Bu da kod yazımı esnasında beyni sürekli kurcalayan, aklımızın bir köşesinde devamlı tutmamız gereken ekstra bir iş demek.

Java 8 ile gelen Optional sınıfı, bizi bu kafa yorma eyleminden bir nebze olsun kurtarmakta. Bilmeyenler için, Optional iki state’i olan bir sınıf: içerisinde bir obje muhafaza edilebilir (Optional.of(value)) ya da içi boş olabilir (Optional.empty()). Optional kullanırken bu iki durumu da göz ardı etmeden kodumuzu yazmamız gerekir.

Kodumuzu Optional kullanarak refactor edelim:

Optional<Student> selectedOpt = Optional.ofNullable(studentMap.get(11));
if(selectedOpt.isPresent()) {
selectedOpt.get().assignHomework();
}

Optional.ofNullable(), aldığı değer null ise Optional.empty(), değil ise Optional.of(value) döndürmekte; bu sebeple get() metodundan gelen null, Optional.ofNullable() ile çevrelendiğinde empty bir Optional<Student> elde etmiş olduk. Bunun sonucunda, NullPointerException fırlatacak selectedOpt.assignHomework() gibi bir kullanım artık mümkün değil, zira elimizde artık Student yerine Optional<Student> nesnesi var ve Optional ile çevrelenmiş olan Student nesnesinin assignHomework() metodunu çağırabilmek için öncelikle Student metodunu erişmeliyiz; bu da üstteki kod örneğindeki gibi Optional’ın get() metodu ile yapılabilir. Bu arada, selectedOpt’un get() metodunu, içerisinde gerçekten bir obje muhafaza edildiğini isPresent() metodu ile kontrol ettiğimiz bir kod bloğunun içerisinde çağırmalıyız; aksi takdirde IDE’nizde ‘Optional.get() without isPresent() check’ şeklinde bir uyarı görebilirsiniz.

Gördüğünüz gibi yavaş yavaş null object kontrolleri üzerinde kafa yorma eylemini üzerimizden atıp başka mecralara devretmeye başladık;

Student selected = studentMap.get(11);
selected.assignHomework(); //throws NullPointerException

Kodumuzun bu versiyonunda hiçbir uyarı olmaksızın NullPointerException alırken,

Optional<Student> selectedOpt = Optional.ofNullable(studentMap.get(11));
selectedOpt.get().assignHomework();

Bu versiyonda IDE’miz “isPresent() kullanmadan get() kullandın, NullPointerException alabilirsin” şeklinde bizi uyarıyor. Göz sağlığımız yerinde olduğu sürece bu belirgin uyarıyı görüp kodumuzu olması gerektiği gibi refactor edebiliriz. Kaldı ki, kullandığımız kod analiz araçları da böyle bir koda müsaade etmeyecek.

Optional kullanarak kodumuzun kalitesini arttırdığımız şüphesiz, ancak kafamızı tam anlamıyla rahatlatmış değiliz. Artık kodumuzu, objenin null olup olmadığının katı bir şekilde kontrolünü yaparak yazmaktan kurtulduk ancak şimdi de null olma ihtimali olan objeleri Optional ile çevrelememeyi unutmamalıyız. Bu da, en az null kontrolleri kadar dikkat edilmesi gereken, zihnin bir kısmını sürekli işgal edecek bir iş. Peki bizi Optional kullanmaya iten neydi?

Bizi Optional kullanmaya iten, Map’in get() metodunun null döndürebilir olmasıydı. Bu metodun null dönmesi demek, Map’te aradığımız elemanın olmaması demek. Map’te aradığımız elemanın olmadığı bilgisini null ile haber vererek aslında hafızamızda tutmamız ve sürekli dikkat etmemiz gereken ekstra bir lojik yaratmış oluyoruz: “null geliyorsa Map’te ilgili eleman yok demektir”. Halbuki bu bilgiyi gayet basit bir şekilde get() metodunun dönüş tipinde belirterek bu yüklerden tamamen kurtulabilir ve get()’in implementasyon detayları ile hiçbir şekilde ilgilenmek zorunda kalmazdık. Ancak mevcut durumda kodumuza, bambaşka bir kodun implementasyon detayı yüzünden kontroller eklemek, kodu güncellemek zorunda kaldık.

Java’da mevcut olmasa da, dönüş tipi Optional<T>, yani bizim örneğimiz için Optional<Student> olan, getOptional() adında bir metoda sahip bir Map implementasyonu kullandığımızı düşünerek kodumuzu güncelleyelim:

Optional<Student> selectedOpt = studentMap.getOptional(11);
if (selectedOpt.isPresent()) {
selectedOpt.get().assignHomework();
}

Artık getOptional()’ın sadece dönüş tipi ile ilgilenmemiz gereken bir noktaya geldik; getOptional() metodunun implementasyonuna gitmemize, sayfa değiştirmemize bile gerek kalmadı. Bu sayede kafamızı meşgul eden hiçbir dış etken olmadan kodumuzu yazmak gibi mükemmel bir kazanım elde ettik. Önceden zihnimizi işgal eden durumları artık düşünmemek bir yana, kullandığımız metotların dönüş tipi bizi nasıl yazmamız gerektiği konusunda yönlendirmeye başladı. Öğretmenin öğrenci numarasını yanlış girme ihtimali artık kod akışının doğal parçası ve bu ihtimali aklımızın bir köşesinde tutmak zorunda değiliz.

Bu türden kazanımları kod yazma rutinimizin tümüne yaymak için yapmamız gerekenler elbette null’lardan kurtulmak ile sınırlı değil. Örneğin, getOptional() metodumuz, görevi olan “istenilen objeyi Optional ile çevreleyip verme”nin dışında başka bir iş de yapıyor olsaydı ki ne yaptığı hiç önemli değil, hala bu metodun implementasyonu ile ilgilenmek zorunda kalır, gönül rahatlığı ile kullanamazdık (side effect). Aynı şekilde, a parametresiyle x sonucunu veren bir fonksiyonumuz, biz yine x sonucunu beklerken bir süre sonra aynı a parametresiyle y sonucunu verseydi bu bizim için hiç hoş olmazdı (pure function).

Scala’nın mucidi Martin Odersky’nin iki gün önce “gördüğüm en açık ‘neden fonksiyonel programlama’ anlatımlarından biri” diyerek paylaştığı yazıda fonksiyonel programlamanın iki hedefinden dem vuruluyor: yerel muhakeme (local reasoning) ve kompozisyon (composition). Kendi bağlamında tamamıyla anlamlı olan (yerel muhakeme) kod parçacıklarını doğru şekilde birleştirdiğimizde (kompozisyon), ortaya çıkan bileşime dair anlayışımız hiçbir şekilde değişmez ve bu sayede oldukça anlaşılır bir codebase’e sahip olmak gibi, veya bir kod parçacığının ne işe yaradığını anlayabilmek için codebase’de dolanarak zamanımızı tüketmek zorunda kalmamak gibi değerli kazanımlar elde ederiz. Bizim yaptığımız da bunun en basit örneklerinden idi: yazdığımız kod parçacığını diğerinin implementasyonuna bağımlı olmaktan kurtarıp yerel muhakeme özelliği kazandırmak ve doğru kod parçacığı ile kompozisyona sokmak.

Aslında yazıya başlarken amacım Java’da fonksiyonel programlamaya yardımcı olan, örneğin yukarıda Java’da bulamadığımız getOptional() metotlu Map implementasyonunu sağlayan Vavr kütüphanesini tanıtmaktı ancak yazı bir “fonksiyonel programlamaya giriş” yazısına evrildi. Bir sonraki yazıda ise Vavr’a yer vermeyi planlıyorum. Son olarak, fonksiyonel programlama konusunda oldukça iyi olan ve bendeki fonksiyonel programlama kıvılcımını ateşleyen çalışma arkadaşım Mustafa Simav’a teşekkür ediyorum. Okuduğunuz için teşekkürler.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Mert Elifoğlu
Mert Elifoğlu

No responses yet

Write a response