Yazılım

Python’da Fonksiyonel Programlamaya Giriş: [1] Nedir ?

{Merhabalar, gençler. Uzun zamandır yazmıyorum. Bunda biraz okulun, biraz da yeni şeyler öğrenmenin katkısı da var. Yazmamamın sebebi de kafamda bir şeyleri oturtmam gerektiği. Bu zaman kendime stack aramakla geçti biraz. Backend stack’ı olgun bir şekilde oturttum, ama frontend stack’ta hala sıkıntılar çekiyorum. Bir backend geliştirici olarak, frontend’in harbi harbi tanrı vergisi olduğunu düşünmeye başladım. Bir şeyi anlamakta sabır edince bir yerden sonra anlayacağımı düşünüyorum, ama belki de frontend böyle değil.

Neyse, biz konumuza dönelim. Neydi? Fonksiyonel programlama. Ne olduğundan önce nerede olduğundan bahsedeyim biraz (parçadan bütüne yani). Fonksiyonel programlama, bir yaklaşım. Ötekilerini belki sık duyduk, belki hiç duymadık: Nesne yönelimli programlama ve adım adım (imperative) programlama yaklaşımları. Yani, kulvarda şu an en çok bu üçü var. Ama aralarındaki yarış gibi bir şey değil. Biri ötekini döver demiyorum yani.

Fonksiyonel programlama, adı üstünde, fonksiyon ağırlıklı bir programlama yaklaşımıdır. Duyar gibiyim?

E?

Yani?

Bunun için mi geldik buraya?

Sakin olun. Aslında adındaki basitlikten biraz daha derin mevzu. Şöyle başlayalım. Nesne yönelimli programlama bize tanıtılırken -hiç değilse bana- şöyle tanıtılmıştı:

Nesne yönelimli yaklaşımda, gerçek dünya sorunlarını, zihin soyutlamalarımızı bilgisayara aktararak çözüyoruz.

İyi de, durum bundan biraz karmaşık. Nesne yönelimlilik hala iyi bir çözüm. Modern web mimari yaklaşımları, örneğin MVC, sırtını nesne yönelimli yaklaşıma dayıyor. Ama nesne yönelimliliğin kendi çapında problemleri de oldu.

Örneğin, her dilde gördüğümüz nesne yönelimli yaklaşım, aynı yaklaşım değil. Evet, çekirdek bir uzlaşı var, ama şeytan ayrıntılardan gülümsüyor.

Örneğin, Java’da bulunan bebişim arayüzleri (interface) Python’da göremiyoruz. Python daha çok, “Oğlum, bu kadar karmaşaya ne gerek var?”a dayanıyor. Java’da statik/dinamik değişkenleri örnekleyiciden (constructor/initializer) önce belirtip (declaretion) değerini örnekleyicide verirken, Python’da örnekleyicide (bu durumda __init__ oluyor kendisi) önce verdiğimiz değişken sınıf, örnekleyiciyle verdiğimiz ise nesne değişkeni oluyor.

Biliyorum, Java/Python’dan çok gittim, ama bir de miraslama (inheritance) sistemine gözatalım. Miraslama, geliştirici açısından çoğu zaman kötü bir uygulama (bad practice) olarak görülmüştür, ama kullandığımız çatılarda bunu uyguluyoruz, zorundayız (örneğin, Django modelleri, class-based view’ları). Java’da bir sınıf, sadece bir tane başka sınıftan miraslama yaparken, Python’da birden fazla yapabiliyoruz.

Bugün ES6 çıkmasına rağmen Javascript’te hala bir tür sorununun olmasına ne demeli? Hacı, yıl 2016. Gerçi, fonksiyonel programlama yaklaşımı da bir nevi bu arkadaşların başından çıktı. “Javascript’te madem OOP’yi adam gibi yapamıyoruz, bari fonksiyon yazalım veya yazabileceğimiz kütüphaneler yazalım.”.

Evet, Javascript ile uğraşan arkadaşlarımızın başından çıkan bu mevzu, bir trend halini aldı. Hatta olur da bir videoya denk gelirseniz FP ile ilgili, ilgili videoda biri (muhtemelen anlatıcı) “sexy” diyordur bir yerde FP için. Kimisi “Geleceğin yaklaşımı.” diyor, “Hadi lan ordan!” diyorlar, adam küsüyor. Kimisi, “Harbiden, bu kadar karıştırmaya ne gerek vardı?” diyor. Sonuçta bu yaklaşım o kadar popüler bir hal aldı ki, sadece fonksiyonel yaklaşımı içeren Closure, Haskell gibi diller çıkmaya başladı. Java’nın nesne yöneliminden kaçır JVM’nin kaynak yönetimi ve performansından vazgeçemeyenler Java’dan Scala’ya geçti. Hatta, nesne yönelimliliğin baştacı Java bile, sekizinci sürümünde fonksiyonel bir API sundu ve devam edeceğini de söyledi.


Aslında durumu basite indirgesek iyi olacak. Bizim bir bilgisayardan istediğimiz nedir? Bir şey ver, evirsin çevirsin, bana başka bir halde geri versin. Olay bu yani. Fonksiyonda da olay bu. Biz girdi veriyoruz, çıktı alıyoruz. Yani, ne uğraşacağız zihinsel yapımızı dökmekle? Bizim asıl işimiz eninde sonunda ilkel türlere (primitive types) düşmüyor mu (integer, string gibi, basit türler yani). Yani, aşağıdaki gibi biraz.

Fonksiyon

Peki, nedir FP’nin artısı:

  • Test Dostu: FP’de testler, OOP’dekine göre daha basit (adım adım yaklaşımda böyle bir şey yok zaten.). Basit, çünkü girdiyi veriyorsunuz ve istediğiniz çıktıyı alıp almadığınızı test ediyorsunuz. Özellikle birim testinde (unit testing) birebir.
  • Daha İyi Performans: Her şey fonksiyon sonuçta. Zaten ilkel veri türleriyle çalışıyorsunuz. Fonksiyon içerisinde barındırdığınız bir değişken, fonksiyon sonunda garbage collector tarafından siliniyor (Python’un da mükemmel namespace yapısı olduğunu ele alın), dolayısıyla hafızada runtime sonuna kadar kalmıyor. Belirtmemde fayda var, eğer bellek okuma/yazma hızınız düşükse, beklediğiniz sonucu alamayabilirsiniz, çünkü fonksiyon her çağırıldığında, veri sıfırdan oluşturuluyor ve fonksiyon sonunda siliniyor.

Peki eksisi yok mu? Var, ama bu biraz bana öznel, siz de aynı düşünür müsünüz, bilmem:

  • Mindset Düşmanı: Web geliştiricisi olduğunuzu düşünelim. Başta MVC, MVT gibi sağlam yaklaşımlar var. ORM gücü var. Bunlar için zihinsel bir soyutlama yapmanız gerekiyor. Dolayısıyla OOP’ye ihtiyacınız oluyor. Bu sebeple belirli yerlerde iki yaklaşımı da kullanıyorum ben.

Tamamdır, ama nedir FP yaklaşımını özel kılan? Bazı önemli noktaları var tabi ki.

Yan Etkilerden Kaçının!

Saf fonksiyon (pure function) dediğimiz şey, içinde yapması gereken dışında hiçbir şey barındırmayan fonksiyon. Sadece girdiyi ya da girdileri al, onlara bir şeyler yap, sonra de çıktıyı döndür. Aşağıdakine bir göz atalım:

123456def topla(a, b):    print(“Bana {} ve {} verdin.”.format(str(a), str(b)))    c = a+b    print(“İkisi toplamda {} ediyor.”.format(str(c))) topla(2,3) # None

Yukarıdaki gibi bir fonksiyon içerisinde print sıkıştırmak, iyi bir FP uygulaması değildir. “Belki şurada küçük bir print vardır.” sevdasından vazgeçelim. Ama evet, bazen kabataslak logging/debugging için araç olarak kullanabiliyoruz, ama logging yapacaksak da, FileHandler seviyesinde olmasına özen gösterelim.

FP'de print gibi yan etkilerden kaçının. Sadece girdileri işlemeye odaklanın.

FP’de print gibi yan etkilerden kaçının. Sadece girdileri işlemeye odaklanın.

Şu, FP ruhuna daha uygun bir kod olabilir:

12345def topla(a,b):    c = a+b    return c topla(2,3) # 5

Fonksiyonların da Bir Adı Var

Çok çok eksiden -çaylakken henüz- bir arkadaşım (?) Python’da her şeyi type() arasına alıyordu ve en çok şaşırdığı şey, fonksiyonun adını verince aldığı çıktıydı:

12345# Boş bir fonksiyon oluşturuyorum.def func():    return 0 type(func) # <class ‘function’>

Demem o ki, Python’da fonksiyonlar da nesnedir ve onların da adı vardır. Bu, şu demek oluyor, bir fonksiyonu girdi olarak başka bir fonksiyona verebilirsiniz. Örneğin şu kodu inceleyelim:

1234567891011def topla(a, b):    return a+b def cikar(a, b):    return a-b def hesapla(fonk, a, b):    return fonk(a, b) hesapla(topla, 3, 5) # 8hesapla(cikar, 3, 5) # -2

Buradaki hesapla() fonksiyonumuzun ilk argümanı, bir fonksiyon, ama bu fonksiyonu, içerisinde çağırıyoruz. Fonksiyonu girdi olarak vermeye girdi olarak fonksiyon (higher-order function) diyeceğiz. İçeride bir şey mi yapacağız, fonksiyonu girdi olarak verelim. Hatta aşağıda logging’i de girdi olarak kullanarak bir deneyelim:

12345678910111213141516import logging logger = logging.getLogger(__name__) def topla(a, b):    return a+b def cikar(a, b):    return a-b def hesapla(log_stream, fonk, a, b):    log_stream(“İşlemi yapıyorum…”)    return fonk(a, b) hesapla(logger.debug, topla, 3, 5) # 8 | debug çıktısı alıyoruz.hesapla(logger.info, cikar, 3, 5) # -2 | info çıktısı alıyoruz.

Bu arada, bir fonksiyonu çıktı olarak da verebilirsiniz.


Bu yazımızın da sonuna gelelim, Bir dahaki konumuz lambda olacak ve daha da ilginci, döngülerden kaçınacağız. Evet, FP’de olabildiğince az döngü yapmamız gerekiyor. Döngüler performansta düşüşe neden olabiliyor.

Bir dahaki yazıda görüşmek üzere…

Comment here