Programlamada “Currying” (Körilemek) Kavramı
Bu yazımızda, bir fonksiyonun birden fazla argümanı tek tek alacak şekilde dönüştürülmesi sürecini ifade eden “currying” kavramını ele alacağız. Bu yapı özellikle, fonksiyonel programlama paradigmalarını destekleyen JavaScript, Python, Haskel gibi çeşitli programlama dillerinde yaygın bir şekilde kullanılmaktadır.
“Currying” Nedir?
“Currying”, fonksiyonların birden çok argümanı tek tek alacak şekilde dönüştürülmesi sürecidir. Kabaca, fonksiyonun her argüman için ayrı ayrı çağrılması durumudur. Yani bir fonksiyonu “curry” ettiğimizde, onu bir dizi tekli argümanlara sahip iç içe fonksiyonlar şeklinde yazarız.
Fonksiyonel programlama dünyasının bilindik kavramlarından biri olan bu kavram, ismini matematikçi ve bilgisayar bilimci Haskell Curry‘den almıştır.
Bu durumu fonksiyonel programlama özelliklerini desteklemesi nedeniyle JavaScript üzerinden örneklendirebiliriz. İlk olarak “Currying” olmadan normal bir fonksiyonu yazalım. Bakınız:
1 2 3 4 5 |
function add(x, y) { return x + y; } console.log(add(5, 3)); // 8 |
Şimdi de “currying” ile fonksiyonumuzu dönüştürelim:
1 2 3 4 5 6 7 8 |
function addCurried(x) { return function(y) { return x + y; }; } const add5 = addCurried(5); console.log(add5(3)); // 8 |
Yukarıdaki kodumuzda, addCurried
isimli bir fonksiyon tanımlanmıştır. Bu fonksiyon, bir argüman alarak, geriye bir başka fonksiyon döndürür. add5
adında bir sabite, bu fonksiyonu 5
değeriyle çağrılarak elde edilen yeni fonksiyon atanmıştır. Böylece, add5
fonksiyonu her çağrıldığında, aldığı değeri 5’e ekleyerek sonucu döndürür. Bunu pekala aşağıdaki gibi de yazabiliriz:
1 2 3 4 5 6 7 |
function add(a) { return function(b) { return a + b; } } console.log(add(5)(3)) //8 |
Yukarıdaki örnekte dikkatinizi çektiyse, console.log(add(5)(3))
ifadesini kullandık. Bu ifade, “currying” tekniği sayesinde add
fonksiyonunun iki set parantezle çağrılmasına izin veriyor. İlk parantez seti add(5)
fonksiyonuna 5
değerini gönderir ve bu fonksiyon bize başka bir fonksiyon döndürür. İkinci parantez seti (3)
ise bu döndürülen fonksiyona 3
değerini gönderir. İç içe bu iki fonksiyon çağrısı sonucunda, 5
ve 3
değerleri toplanarak 8
sonucunu elde ederiz. Bu yaklaşım, fonksiyonel programlamada sıkça karşımıza çıkan bir yöntemdir ve fonksiyonları daha modüler bir şekilde kullanmamıza olanak tanır.
Peki o halde, bu “currying” denilen şey ne işe yarar ve JavaScript gibi bir dilde nasıl kullanılır? Aslında daha modüler ve tekrar kullanılabilir kod yazmamızı sağlar. Bir fonksiyonu “körileyerek” daha spesifik amaçlar için alt fonksiyonlar oluşturabiliriz.
Bir e-ticaret platformunu düşünelim. Bu platformda, kullanıcılara indirim uygulayacak bir fonksiyonumuz olsun. Belki bazı kullanıcılara belirli ürün kategorileri için, belki de kullanıcının alışveriş geçmişine dayalı olarak farklı indirim oranları uygulamak istiyoruz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function applyDiscount(price, discount) { return price - (price * discount); } // Öğrenci için %10 indirim let studentPrice = applyDiscount(100, 0.10); // 100 - 10 = 90 // Emekli için %15 indirim let retireePrice = applyDiscount(100, 0.15); // 100 - 15 = 85 // VIP müşteri için %20 indirim let vipPrice = applyDiscount(100, 0.20); // 100 - 20 = 80 console.log(`Öğrenci fiyatı: ${studentPrice}`); // Çıktı: Öğrenci fiyatı: 90 console.log(`Emekli fiyatı: ${retireePrice}`); // Çıktı: Emekli fiyatı: 85 console.log(`VIP fiyatı: ${vipPrice}`); // Çıktı: VIP fiyatı: 80 |
Eğer öğrencilere %10, emeklilere %15 ve VIP müşterilere %20 indirim uygulamak istiyorsak, her seferinde bu fonksiyonu farklı oranlarla çağırmamız gerekecektir. Eh bir de indirim oranlarını birçok yerde kullanıyorsak işler çığırından çıkabilir. İşte “currying” kullanarak bu fonksiyonu aşağıdaki gibi dönüştürebiliriz:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
function getDiscountedPrice(discount) { return function (price) { return price - price * discount; }; } const studentDiscount = getDiscountedPrice(0.1); const retireeDiscount = getDiscountedPrice(0.15); const vipDiscount = getDiscountedPrice(0.2); console.log(studentDiscount(100)); // 90 console.log(retireeDiscount(100)); // 85 console.log(vipDiscount(100)); // 80 |
Artık, bir öğrenci için indirim uygulamak istediğimizde studentDiscount(100)
şeklinde bir çağrı yapabiliriz. Bu, 100 birimlik bir ürün için %10 indirim uygulayacaktır.
Yukarıdaki örneğimizdeki gibi “currying” (körleme) tekniği kullanılarak fonksiyonları modülize ederiz. Her fonksiyon, belirli bir işlevi veya görevi yerine getirir. Bu yaklaşım, fonksiyonların belirli bir amaç için “uzmanlaşmasını” sağlar.
Örneğin, getDiscountedPrice
fonksiyonuyla, belirli bir indirim oranı için fonksiyon oluşturabiliriz. Bu oluşturulan fonksiyonlar (örn. studentDiscount
, retireeDiscount
, vipDiscount
), sadece indirim uygulama görevini üstlenir.
Bu yaklaşım, kodun daha modüler ve okunaklı hale gelmesini sağlar. Özellikle aynı işlevi farklı parametrelerle birçok yerde kullanmamız gerektiğinde, “currying” sayesinde fonksiyonları daha anlamlı ve kullanışlı hale getirebiliriz.
Özetle, “currying” ile daha spesifik amaçlar için alt fonksiyonlar oluşturabiliriz. Bu da haliyle, kodun daha okunabilir ve bakımının daha kolay olmasını sağlar.