Nedir bu fonksiyonel programlama?

Türerkan İnce
6 min readSep 19, 2019

--

kid writing formulas on black board
Örtmenim, fonksiyonel sınavda çıkacak mı? — Çıkacak evladım, otur.

Nesne yönelimli (object oriented) programlama o kadar standartlaşmış durumda ki çoğumuz için iyi ve doğru kod yazmak anlamına geliyor. Balık suda yüzdüğünü bilmezmiş, suyun dışını görmediği için. Biz de object oriented’in dışını görmediğimiz için onun bir öznel tercih olduğunu anlamıyoruz.

Peki kafamızı sudan çıkardığımızda ne göreceğiz?

Uzun yıllardır akademik çevrelerde öne çıkan fonksiyonel programlama (functional programming) artık yavaş yavaş gündelik hayata giriyor. Gelecekte iyi kod yazmak fonksiyonel kod yazmak olacak, bunu öngörmek zor değil. Javascript’te Promise, React.Js, C#’ta LİNQ gibi bazı tanıdık isimler bize ne olduğunu bilmeden fonksiyonel programlama konseptlerini çoktan kullandırıyor bile.

Nedir bu fonksiyonel programlama?

Bir bilgisayar programı dışarıdan bazı girdileri alır, onlar üzerinde işlem yapar ve bazı çıktılar üretir. Matematiksel bir gözle bakarsanız aslında bütün bir programın matematikteki bir fonksiyona benzediğini görürsünüz.

program(girdiler) = çıktılar

Fonksiyonel paradigmada programa kuşbakışı tepeden yaklaşırsınız (bir nevi tümden gelim). İlk başta tüm girdileri verdiğiniz ve tüm çıktıları aldığınız tek bir metotmuş gibi. Daha sonrada mantıksal olarak parçalamaya başlarsınız. Fonksiyonel program birbirini çağıran, girdilerini istenen çıktıya dönüştüren ufak programcıklardan oluşan bir ağaç olarak şekillenir.

Yani ideal bir fonksiyonel kod girdilerini alır, bunları işlevlerine göre parçalanmış fonksiyonlara dağıtır, onlar da daha da ufak bir şekilde parçalanmış fonksiyonlara dağıtır ve böyle gider. Dönen değerler başa doğru toplanır ve en sonunda programın çıktısını oluşturur.

Ağacın en altındaki fonksiyonlar o kadar küçüktür ki, sadece tek bir işlevi vardır, bir kaç satırdan ibarettir ve tek seferde okunarak anlaşılır.

Aşağıdan yukarı bakarsak fonksiyonel programlama tek işlevli küçük fonkisyonların komposizyon (function composition) prensibi ile birleştirilmeleri ile inşa edilen yazılımdır.

Buraya kadar kocaman bir laf salatası gibi gelmiş olabilir, kusura bakmayın. Şimdi yavaşça zurnanın zırt dediği yere geliyoruz.

Dikkat ederseniz matematikteki fonksiyonlar sadece girdileri üzerinde işlem yapar. Girdileri haricinde bir değişkene referans veremez, bir değişkenin değerini değiştiremez. Fonksiyonel kod da aynı şekilde sadece girdileri üzerinde işlem yapar, girdileri haricinde bir veriye erişmez, girdileri dahil herhangi bir değişkeni değiştirmez. Kısacası fonksiyonel kod yan etki yaratmaz!

Yan etki nedir?

Inception’da gittikçe alt seviyelere iniyoruz. Fonksiyonel programlama sevenler yan etkiden vampir görmüş gibi korkar. Yan etkileri mümkün olduğunca azaltmaya, bir yerde birleştirmeye ve izole etmeye çalışırlar. Peki nedir yan etki?

Bir fonkisyon veya metot doğrudan kendisine verilmiş parametreler haricinde birşeye erişiyorsa veya değişiklik yapıyorsa bu bir yan etkidir. Çünkü parametre olmayan dış veri kontrolümüz dışında değişebilir, bu durumda fonksiyonumuz aynı girdilerle farklı çıktılar üretebilir hale gelir. Dahası bizim dışarıda yaptığımız değişiklik üçüncü bir fonksiyonun çıktısını da etkileyebilir.

Kendi dışındaki veride değişiklik yapabilen bir fonksiyon değiştirdiği yerlerle girift bir bağ kurar, kodda değişiklik yapmak istediğinizde etkilenicek yerleri düşünmekten başınız ağrır.

Zaten büyük çaplı yazılım projelerinin çözümsüzlük ve verimsizlik duvarlarına çarpmasının nedeni budur. Dikkatsizce gelişmesine izin verilen yan etki ilişkileri programda yapılacak herhangi bir değişikliğin sonuçlarının kestirilmesini imkansız hale getirir. Bir birim zamanda yazılan yeni özelliğin ardından on birim debug yaparsınız. Kod, nasıl çalıştığı bilinmeyen, tesadüflerle ve dualarla yürüyen bir ip yumağına dönüşür.

Fonksiyonel programlama yan etkileri yasaklar. Elinden geldiğince. Elinden geldiğince diyorum çünkü hiç yan etkisi olmayan program bir işe de yaramaz. Network ile iletişim kurmayan, grafik arayüzü olmayan, dosya okuyup yazmayan, kendi iç durumunu saklamayan bir program basit bir hesap makinesinden öteye gidemez.

Ama yan etkileri olabildiğince merkezileştirir, kodun en büyük kısmını saf fonksiyonlardan oluşur halde tutabilirsek kodu yönetmemiz inanılmaz kolaylaşır.

Fonksiyonel program örneği.

Fonksiyonel Programlamanın Şartı Kaçtır?

  1. Fonksiyon parametreleri hariç veri üzerinde işlem yapmaz.
  2. Fonksiyon parametrelerini veya dışındaki veriyi değiştirmez.
  3. Fonksiyon sadece değer döndürerek sonuç üretir.
  4. Fonksiyon sadece bir mantıksal işlevi yerine getirir.
  5. Fonksiyon aynı girdilerle hep aynı çıktıyı üretir.
  6. Fonksiyon mümkün tüm girdiler için mutlaka mantıklı çıktılar üretir.

Bazen günün tarihi, o anki saat, rastgele sayı üreteçleri gibi yan etki üreten şeyleri dışarıdan parametre olarak alamayız, fonksiyonun içinde referans vermemiz gerekir. Bu birinci şartı üzer.

Pratikte yan etkileri tamamen tasfiye edemediğimiz için ikinci şart konusunda esnemek zorunda kalacağımız açıktır.

Bazen sırf girdileri değiştirmemiş olmak adına veri kopyalamak istemeyiz. Belki girdimiz çok büyük bir veridir ve kopyalamak yerine üzerinde değişiklik yapmak icap eder. Bu durumda ikinci şart yine yara alır.

Bazen dosyaya bişey yazmamız, networkten birşey göndermemiz gerekir. Bunlar değer döndürerek yapılabilen işlemler olmadığı için üçüncü şartın esnemesi gerekir (aslında kategori Teorisinde (category theory) monad olarak isimlendirilen yapılar aracılığıyla yan etkileri değer olarak ifade etmek mümkün).

Bu bahsettiğimiz özel durumların hepsi aslında aynı, yan etki üretmekten kaçınamadığımız durumlar. Peki programımızı tasarlarken yan etkilere nasıl yaklaşmalıyız?

Yan Etkilerin Programdaki Yeri

Yan etkilerle başetmenin yolu, programın yukarıdan aşağı doğru çağırılan fonksiyonlar ağacında yan etkileri yukarı doğru ittirmek ve merkezileştirmektir. Yani en uçta, en alttaki minik fonksiyonlarınız yan etkiyi gerçekleştirmek yerine döndürdüğü değerle yukarıdaki fonksiyonlara yan etkinin gerçekleştirilmesi gerektiğini söyler. Yan etkiyi idare eden üstteki fonksiyonlar mümkün olduğunca hesaplama yapmaz, sadece yan etkilere odaklanır. Yan etkiler yukarı taşındıkça birleşirler, aynı yan etki için birden fazla yere kod yazmamış oluruz. Böylece yan etkilerin toplam kod içindeki yeri azalır.

İstediğiniz yan etki ister bir veritabanı çağrısı, ister bir konsol çıktısı, ister bir dosya işlemi olsun; onu sadece yan etkilerden sorumlu bir yere hapsetmek ve olabildiğince az yerden çağırmak istersiniz. Bir çok N katmanı nesne tabanı projede görebileceğiniz Data Access Layer yapıları kısmen buna denk gelmektedir. Ama çoğunlukla DAL çağrılarını sayıca azaltmaya dikkat etmeyiz.

Yan Etkiye Örnek

Yan etkilerin izole edilmesi konusunda çoğumuzun denk geldiği en güzel örnek Javascript dünyasının asenkron işlemleri Promise’ler ile idare etmeye başlamış olmasıdır. Promise’ler asenkron işlemleri bir değer (promise değişkeni) olarak soyutlaştırır. asenkron işlemin sonucuyla işlem yapıp onu değiştiren kod parçaları üst seviyedeki promise kütüphanesince idare edilir, programcı asenkron işlemin ve sonuçlar üzerinde yapılacak hesaplamaların planlanmasına karışmaz, sadece promise’in sonuna bir then koyar. veride değişikliğe gidecekse bunu mevcut değişkenleri değiştirerek değil, yeni değer döndürerek yapar. Dönen değerin asenkron işlemin bir sonraki adımına taşınması yine üst seviyedeki promise kütüphanesi tarafından halledilir.

Eski javascript’i kullanmış herkes promise’lerin ne kadar büyük bir rahatlama yarattığına şahittir. Dikkat ederseniz bir API tasarımı olarak promise’ler yukarıda belirlediğimiz şartlara oldukça güzel uyuyor. Sizce bir nedeni olsa gerek mi?

Kompozisyon’a örnek

Fonksiyonları komposizyonla birleştirme, veride değişiklik yapmak yerine yeni değer dönmek ve tüm girdiler için mantıklı sonuç üretmek gibi konseptlerin bir araya geldiği bir başka tanıdık kullanım da birçok dilde karşımıza çıkan map, filter ve reduce fonksiyonlarıdır. Çoğu nesne yönelimli programlama dili en azından bu üçünü sağlayarak kısmi bir fonksiyonel yaklaşıma izin veriyor. Bunların kullanımına örnek olarak C# ve Linq’i örnek vermek istiyorum. Ne yazıkki Microsoft bu üç fonksiyonun ismini değiştirip SQL’e benzettiyse de mantık aynı.

Microsoft’un isimlendirmesi ile Select map’e, Where filter’a, Aggregate de reduce’a denk geliyor. Dikkat ederseniz bunların SQL’e benzemesi raslantısal değil. SQL de tıpkı fonksiyonel programlama gibi girdileri çıktılara eşleyen (map), yan etki yaratmayan (side effect free, immutable), sorgu cümleciklerini kompoze eden (functional composition) bir programlama modeline sahip. Hatta bu noktada bir SQL örneği vermeye dahi gerek kalmadığını düşünüyorum, yukarıdaki örneği öyle hayal etmek hiç zor değil.

Yukarıdaki aynı zamanda kompozisyona çok güzel bir örnek. Kütüphaneden aldığımız map, filter ve reduce ile kendi fonksiyonlarımızı harmanlayarak daha komplike işler beceren bir fonksiyon yaratmışız. (Eğer “göremedim nerede ki o?” derseniz, bir de .Select ile başlayan fonksiyon çağrıları silsilesini bağımsız bir fonksiyon olarak yazıp isimler’i bir parametre olarak verdiğimizi düşünün. İşte o, kompozisyon ile yarattığımız yeni fonkisyonun bağımsız hali olurdu.)

Peki Bu Öğrendiklerimiz İş Hayatında İşe Yarayacak mı?

Dikkat ederseniz yukarıda yazdığım hiçbir şey nesne yönelimli programlamaya aykırı değil. Yani klasik bir nesne yönelimli yazılım mimarisi içinde de fonksiyonel kod yazabilirsiniz. Atabileceğiniz en kolay adım yazdığınız her kod parçası için yan etkileri minimize etmeye çalışmak. Mecbur olduğunuz yan etkileri de hesaplama ve veri dönüşümlerinden bağımsız sınıflarda izole edebilirsiniz. Girdi değerlerinde değişiklik yapmaktan kaçınırsanız süper olur.

Nesne yöneliminde hiç olmazsa gizli this parametresini değiştirmeye mecbur kalacaksınız. Bu durumda yapabileceğiniz en güzel şey saf fonksiyonlarla yan etkileri ayrı tutmak. Yani türkçesiyle, this’te değişiklik yapan metotlardan değer döndürmemeye çalışın, mümkünse void olsunlar. Dönmek istediğiniz değerleri üreten başka fonksiyonlar yazabilirsiniz, onlar da this’te değişiklik yapmazlar.

Eğer parametrelerde değişiklik yapmak yerine bağımsız değerler döndürerek çalışırsanız bir bonus olarak multi-threaded kodlarınız daha az hatalı olur. Başka thread’lerin aynı objeye erişirken tutarsız iki farklı halini görme olasılığından kurtulmuş olursunuz (Araştırmak isterseniz: immutability, multi threading)

Sonuç

Fonksiyonel programlama artık akademik çevrelere ve Haskell, OCaml, F#, Rust, Reason gibi ana akım dışı dillerle sınırlı kalmıyor. Fonksiyonel programlama bir hipster hobisi değil. Haskell de bir Karaköy kahvecisinin adı değil:)

Bütün bunların senelerce prosedürel ve nesne yönelimli kod yazmış beyne başta çok uçuk geldiğini anlayabiliyorum. Ama el alışınca ne güzel akıyor inanamazsınız. Hem kompleksiteyi kontrol altında tutuyor, hem refactoring’i kolaylaştırıyor hem de kodu anlamayı kolaylaştırıyor.

Benim bir makale kapsamında fonksiyonel programlamayı baştan uca öğretmem mümkün değil. Daha monad’lardan bahsetmedik bile. Ama araştırmak ve üzerine düşünmek için bir başlangıç noktası sağlayabilirsem ne mutlu bana.

Şimdi ona ufak bir sürpriz yapın, class’ının arasına bir demet fonksiyonel kod sıkıştırın:)

Detaylı araştırmalarınız için anahtar kelimeler: functional programming, functional composition, pure functions, immutability, side effect free programming, category theory, monads.

Not: bundan sonraki yazıda kompozisyon konusunu işlememe ne dersiniz?

--

--