Fonksiyonel Programlamaya Giriş: Map, Reduce ve İmmutability Örnekleri

Fonksiyonel Programlamaya Giriş

Fonksiyonel Programlamaya Giriş: Map, Reduce ve İmmutability Örnekleri

Bu yazı, fonksiyonel programlamaya giriş için üç temel fikri pratik şekilde açıklar: map, reduce ve immutability (değişmezlik). JavaScript’te Array.map/Array.reduce ve Python’da functools.reduce örnekleriyle; hangi durumda hangisinin daha okunur ve güvenli olduğunu adım adım görürsünüz.
Fonksiyonel Programlamaya Giriş: Map, Reduce ve İmmutability Örnekleri

Fonksiyonel programlama (FP), “veriyi adım adım değiştirmek” yerine “veriden yeni değerler üretmek” fikrine dayanır. Tamamen FP yazmak zorunda değilsiniz; ama map, reduce ve immutability (değişmezlik) gibi yapı taşları, daha tutarlı veri dönüşümleri yazmanıza yardımcı olur.

Bu rehberde JavaScript ve Python üzerinden pratik örneklerle ilerleyeceğiz. Amaç; kavramları ezberlemek değil, ne zaman kullanacağınızı ve ne zaman kaçınmanın daha iyi olacağını netleştirmek.


Fonksiyonel yaklaşımın 3 temel fikri

1) Saf (pure) fonksiyon: aynı girdi, aynı çıktı

Saf fonksiyon ideal olarak (1) aynı girdiye aynı çıktıyı üretir ve (2) dış dünyayı (global değişken, dosya, ağ vb.) değiştirmez. Her yerde mümkün olmasa da, özellikle veri dönüştürme katmanlarında saf fonksiyonlar kodu sadeleştirebilir.

Takeaway: Dönüşümleri saf tutabildiğiniz ölçüde test yazmak ve davranışı tahmin etmek kolaylaşır.

2) Higher-order functions: fonksiyon alan / döndüren fonksiyonlar

map ve reduce, fonksiyon alıp koleksiyon üzerinde çalışan klasik higher-order örnekleridir: dönüşüm mantığını bir fonksiyon olarak verirsiniz, işlem “koleksiyona uygulanır”.

Takeaway: Dönüşüm mantığını küçük fonksiyonlara ayırmak, okuması kolay akışlar üretir.

3) Immutability: var olanı değiştirme, yenisini üret

İmmutability (değişmezlik), akademik tanımıyla “bir değer oluşturulduktan sonra değiştirilememesi” yaklaşımıdır. Princeton ders notlarında şu şekilde ifade edilir: “A data type is immutable if you can’t change a data-type value once created.” (Kaynak: Princeton: Designing Data Types.)

  • Pratikte ne kazandırabilir? Paylaşılan veriyi “yerinde değiştirmek” yerine yeni değer üretmek, yan etkiyi sınırlamaya ve değişikliklerin izini sürmeye yardımcı olabilir.
  • Bedeli ne olabilir? Daha fazla kopyalama/nesne üretimi, özellikle büyük verilerde maliyet yaratabilir.

Takeaway: Değişmezlik, her yerde katı bir kural değil; kritik veri akışlarında yan etkileri azaltmak için bir araçtır.


JavaScript’te map(): bir diziden yeni bir dizi üretmek

MDN’ye göre Array.prototype.map(), dizideki her eleman için bir fonksiyon çalıştırır ve sonuçlardan yeni bir dizi döndürür. Kaynak: MDN Array.prototype.map().

map ne zaman ideal?

  • Bir listeyi aynı uzunlukta başka bir listeye dönüştürmek istediğinizde (ör. fiyatları vergi ekleyerek dönüştürmek).
  • UI için view model üretirken.
  • API’den gelen ham veriyi “temizleyip” yeni formata çevirirken.

JavaScript map örneği (dönüşüm)

Girdi: [10, 20, 30]
Hedef: Her elemanı 2 ile çarpıp yeni dizi üretmek

Kod:
const numbers = [10, 20, 30];
const doubled = numbers.map(n => n * 2);
// doubled: [20, 40, 60]
// numbers: [10, 20, 30]

  • İpucu: Dönüşümünüz tek cümleyle anlatılamıyorsa, callback’i ayrı bir fonksiyon olarak isimlendirin.

Takeaway: map, “liste → liste dönüşümü” için en anlaşılır seçeneklerden biridir.

map kullanırken sık yapılan hatalar

  • Yan etki için map kullanmak: Sadece log basmak veya dışarıdaki bir diziyi doldurmak için map tercih edilmez.
  • Return unutmak: Blok gövdeli arrow function’larda return unutulabilir.

JavaScript’te reduce(): bir diziyi tek bir değere indirmek

Array.prototype.reduce(), diziyi tek bir sonuç değerine “indirgemek” için kullanılır. MDN, accumulator (biriktirici) mantığını ve initialValue (başlangıç değeri) parametresini açıklar. Kaynak: MDN Array.prototype.reduce().

reduce’u zihinde canlandırma

reduce’u, “Bir kutu (accumulator) var; listedeki her elemanı sırayla alıp bu kutuyu güncelliyorum; en sonda kutuda tek bir sonuç kalıyor.” şeklinde düşünebilirsiniz.

Takeaway: reduce, “biriktire biriktire tek sonuca gitme” kalıbını geneller.

Önemli davranış: boş dizi + initialValue

  • MDN’nin açıkladığı davranışa göre, boş bir dizi üzerinde initialValue vermeden reduce çağırmak hata (TypeError) oluşturur.
  • Bu yüzden reduce yazarken initialValue seçimini bilinçli yapmak iyi bir alışkanlıktır.

reduce neden güçlü, neden riskli?

  • Güçlü: Toplam, çarpım, en büyük değer, sözlük/harita (object) üretme, gruplama gibi işleri tek bir şablonda toplar.
  • Riskli: Her şeyi tek bir reduce bloğuna sıkıştırmak, okunabilirliği düşürebilir. Bu durumda işlemi map/filter gibi daha açık adımlara bölmek çoğu zaman daha anlaşılır olur.

JavaScript reduce örneği 1: toplam

Kod:
const cartTotals = [19.99, 5.00, 12.50];
const sum = cartTotals.reduce((acc, price) => acc + price, 0);
// sum: 37.49

Takeaway: Toplam gibi tek sonuç üreten işlemlerde reduce, doğru initialValue ile nettir.

JavaScript reduce örneği 2: gruplama (group by)

Girdi: Kullanıcıları role göre grupla

Kod:
const users = [
{ name: 'Ada', role: 'admin' },
{ name: 'Linus', role: 'user' },
{ name: 'Grace', role: 'admin' }
];

const byRole = users.reduce((acc, u) => {
const key = u.role;
const existing = acc[key] ?? [];
return { ...acc, [key]: [...existing, u] };
}, {});

  • Bu örnekte her adımda yeni obje ve yeni dizi üreterek “mutasyon yapmama” yaklaşımını koruyoruz.
  • Bu stil, bazı kod tabanlarında daha öngörülebilir bir akış sağlayabilir; ancak ek kopyalama maliyeti de doğurabilir. İhtiyaç varsa ölçüm yapın.

Takeaway: reduce ile karmaşık sonuçlar üretmek mümkün; ama okunabilirlik ve maliyet dengesini gözetin.

reduce ne zaman tercih edilmemeli?

  • İşlem birden fazla dönüşüm adımı içeriyorsa ve tek reduce bloğu “her şeyi yapıyorsa”.
  • Ekipte yeni başlayanlar varsa ve mantık tek bakışta anlaşılmıyorsa.
  • Aslında map/filter veya daha açık bir döngü ile daha net ifade edilebiliyorsa.

map vs reduce: hızlı karşılaştırma

Kriter map() reduce()
Çıktı tipi Her zaman yeni bir dizi Tek bir değer (number, object, array, vb.)
En iyi kullanım Eleman bazlı dönüşüm Toplama, biriktirme, birleştirme, gruplama
Okunabilirlik Genellikle daha yüksek Tek blokta büyürse düşebilir
Değişmezlik yaklaşımıyla uyum Doğal olarak uyumlu (yeni dizi üretir) Uyumlu olabilir; accumulator’ı mutasyona uğratmadan yazmak dikkat ister

Takeaway: map “dönüştürür”, reduce “birleştirir/indirger”.


Python tarafı: map ve functools.reduce

Python’da reduce, Python 3’te built-in olmaktan çıkarılıp functools modülüne taşınmıştır. Kaynak: Python docs: functools.

Python map örneği

Kod:
numbers = [10, 20, 30]
doubled = list(map(lambda n: n * 2, numbers))
# doubled: [20, 40, 60]

Aynı işi Python’da çoğu zaman daha okunur biçimde şöyle de yazarsınız:

Kod:
doubled = [n * 2 for n in numbers]

Takeaway: Python’da da ana fikir aynı: “girdiyi yerinde değiştirmek yerine yeni çıktı üretmek”.

Python functools.reduce örneği

Kod:
from functools import reduce

cart_totals = [19.99, 5.00, 12.50]
sum_total = reduce(lambda acc, price: acc + price, cart_totals, 0)
# sum_total: 37.49

  • Not: Python’da sayıları toplamak için pratikte çoğu zaman sum(cart_totals) daha net bir tercihtir.

Takeaway: reduce, “özel bir biriktirme” gerçekten gerektiğinde değer kazanır.


İmmutability (değişmezlik) pratikte nasıl düşünülmeli?

İmmutability’yi “hiçbir şeyi asla değiştirme” şeklinde katı bir kural gibi değil, kritik veri akışlarında yan etkiyi sınırlama yaklaşımı olarak ele almak çoğu projede daha gerçekçidir.

  • Tanım (kaynaklı): Oluşturulan değerin değiştirilememesi (Princeton). Kaynak
  • Pratik bakış: Değişmez parçalarla çalışmanın operasyonel/delivery tarafında tekrarlanabilirliği kolaylaştırabileceği fikri, Martin Fowler’ın “immutable server” yaklaşımında tartışılır (uygulama kodundaki immutability ile bire bir aynı konu değildir). Kaynak

Takeaway: Değişmezlik, “değişikliği yönetme” yükünü azaltmaya yönelik bir tasarım tercihi olabilir; kapsamını proje ihtiyaçları belirler.

JavaScript’te immutability örnekleri (temel)

Diziye eleman ekleme (mutasyon yerine yeni dizi):

Kod:
const items = ['a', 'b'];
const nextItems = [...items, 'c'];
// items aynı kalır, nextItems yeni dizidir

Objede alan güncelleme (mutasyon yerine yeni obje):

Kod:
const user = { name: 'Ada', plan: 'free' };
const upgraded = { ...user, plan: 'pro' };
// user aynı kalır, upgraded yeni objedir

Dikkat: iç içe yapılarda “shallow copy” tuzağı

  • Spread operatörü çoğu durumda yüzeysel kopya üretir.
  • İç içe objelerde alt seviyedeki referanslar paylaşılabilir.
  • Doğru strateji (veri şekli, performans ihtiyacı, ekip standardı) projeye göre değişir.

Takeaway: Değişmezlik hedefiniz varsa, iç içe yapılarda kopyalama stratejisini bilinçli seçin.


Adım adım: map/reduce ile daha “fonksiyonel” yazmak için mini kontrol listesi

  • 1) Dönüşümünüzü isimlendirin: Karmaşık inline fonksiyonlar yerine küçük yardımcı fonksiyonlar okunabilirliği artırabilir.
  • 2) map sadece dönüşüm için: Yan etki (log, dış değişken güncelleme) ağırlıktaysa yaklaşımı sadeleştirin.
  • 3) reduce kullanıyorsanız initialValue düşünün: Boş dizi ve tip tutarlılığı açısından kritiktir (MDN).
  • 4) Okunabilirliği ölçüt alın: Tek bir reduce bloğu uzuyorsa, işlemi daha küçük adımlara bölün.
  • 5) Küçük testlerle doğrulayın: Dönüşüm fonksiyonları küçük girdilerle kolay test edilir.

Takeaway: Amaç “en kısa kod” değil, ekipçe kolay okunan ve değiştirilen kod.


Sonuç: FP bir hedef değil, araç seti

Fonksiyonel programlama, günlük geliştirmede “ya hep ya hiç” yaklaşımı gerektirmez. map ile güvenli dönüşümler yapmak, reduce’u doğru yerde kullanmak ve immutability ile yan etkileri sınırlamak; hem JavaScript’te hem Python’da daha bakımı kolay kod üretmenize katkı sağlayabilir.

Pratik egzersiz: Elinizdeki gerçek bir veri akışını seçin (ürün listesi, kullanıcı etkinlikleri, log kayıtları). Önce döngüyle yazın, sonra map/filter/reduce ile daha küçük parçalara bölerek tekrar yazın. Hangisi ekip arkadaşınızın daha hızlı anlayacağı bir çözümse, çoğu zaman “daha iyi” olan odur.