Giriş
Serinin son yazısında, currying metodundan ve C# ile nasıl implemente edileceğinden bahsedeceğiz.
Currying, birden fazla parametre alan bir fonksiyonun, tek parametre alan fonksiyona dönüştürülmesi işlemidir.
İhtiyacımız, serinin ilk yazısından da hatırlayacağınız üzere fonksiyonel programlamanın temellerinden “fonksiyonların fonksiyon döndürebilmesi”.
Lise Matematik Bilgilerimizi Hatırlayalım
Matematik fonksiyonları ile currying yöntemini açıklamak çok daha kolay.
Elimizde x ve y parametreleri alan bir f(x,y) fonksiyonumuz olsun.
f(x,y) = x2 + y2
Bu fonksiyonu çağırabilmek için x ve y olarak iki farklı parametre göndermemiz gerekiyor.
f(3, 4) = 32 + 42 = 25
- Bu fonkiyonu
curryişlemine soktuğumuzda, sadecexparametresi alan yeni bir fonksiyon dönmeli, - Dönen bu fonksiyona
xiçin3değerini verdiğimizde,yparametresi alan ikinci bir fonksiyon dönmeli, - Bu ikinci fonsiyona
yiçin4değerini verdiğimizde nihayet25sonucunu vermelidir.
Kısmî Fonksiyon Nedir?
Kısmî (
partial) fonksiyon, parametrelerin bazılarının metoda daha sonra geçilebildiği fonksiyonlardır.
Beş parametreli bir fonksiyonun ilk parametresini şimdi, kalan dört parametreyi daha sonra geçebilmek için Func delegesini kullanacağız.
Curried Fonksiyon
Curried fonksiyonlar kısmî fonksiyonların özel bir türüdür. Dönüştürülen fonksiyonlar daima tek parametre aldığında curried, birden fazla parametre aldığında ise partial olarak adlandırılır.
C# İle Basit Bir Currying Uygulaması
Elimizde farazi bir KDV hesaplama fonksiyonu olsun.
1decimal calculateTax(decimal money, decimal taxRate) {
2 return money * taxRate;
3}
Bu fonksiyonun imzası aşağıdaki şekilde olur:
1Func<decimal, decimal, decimal> calculator = calculateTax
2// money taxRate tax
Yukarıda, iki parametreli bu fonksiyonun iki dönüşüm geçirerek, iki adet ara fonksiyonun oluşacağını belirtmiştik. Diğer ifadeyle 25 sonucuna ulaşabilmek için fonksiyonumuz iki currying aşamasından geçecek.
1Func<decimal, Func<decimal, decimal>> curried = curry(calculateTax);
Curried bir fonksiyonun kullanımı aşağıda şekilde.
1decimal result = curried(500)(0.18);
Neden Böyle Bir Kullanıma İhtiyacımız olsun?
calculateTax(500.00, 0.18) varken neden curried(500.00)(0.18) şeklinde bir kullanıma ihtiyacımız olsun?
Genel amaçlı bir fonksiyondan, yeni fonksiyonlar türeterek özel amaçlar için benzer fonksiyonlar tekrarlarının önüne geçerek kod kalitesini artırmak için.
Currying Olmadan
1decimal kdv = calculateTax(500.00, 0.18);
2decimal otv = calculateTax(500.00, 0.05);
3decimal trtPayi = calculateTax(500.00, 0.013);
Currying Kullanarak
1var curried = curry(taxCalculator);
2Func<decimal, decimal> taxCalculator = curried(500.00);
3
4var kdv = taxCalculatorFor500(0.20);
5var otv = taxCalculatorFor500(0.05);
6var trtPayi = taxCalculatorFor500(0.05);
Daha fazla kod yazdık, fakat aşağıdaki fonksiyonların tekrar kullanılabilir olduklarına dikkat edin:
taxCalculatortaxCalculatorFor500
taxCalculator genel amaçlı, taxCalculatorFor500 ise özel amaçlı türetilmiş bir fonksiyondur. Yaptığımız işlem, fonksiyonun ilk parametresi 500.00 değerini önceden geçerek kısmî bir fonksiyon türetmek.
Devletimiz, yeni bir vergi çıkardığında tek yapmamız gereken, taxCalculatorFor500 fonksiyonunu vergi oranı ile çağırarak yeni vergi tutarını hesaplamak olacak.
İki Parametreli Bir Fonksiyon İçin Jenerik Curry Fonksiyonu
Aşağıdaki kodu language-ext 1 kütüphanesinden aldım.
10 parametreli bir fonksiyon için curry ihtiyacınız olursa 115. satıra bakabilirsiniz.
1/// <summary>
2/// Curry the function 'f' provided.
3/// You can then partially apply by calling:
4///
5/// var curried = curry(f);
6/// var r = curried(a)(b)
7///
8/// </summary>
9[Pure]
10public static Func<T1, Func<T2, R>> curry<T1, T2, R>(Func<T1, T2, R> f) =>
11 (T1 a) => (T2 b) => f(a, b);
Sonuç
Kısmî fonksiyonlar için language-ext kütüphanesinin wiki sayfasını 2 okumanızı tavsiye ederim.
Tüm design pattern için geçerli olduğu gibi, currying ve partial functions patternlerini uygulayacağınız yerleri iyice düşünmelisiniz.
Language-Ext, Haskell fonksiyonel yöntemlerini C#‘a uyarlayan ilginç bir kütüphane. ↩︎