Giriş
Önceki yazımızda fonksiyon delegelerinden bahsettik. Bu yazımızdan itibaren fonksiyonel programlama yöntemlerini C# ile uygulama yöntemlerinden bahsetmeye başlayacağız.
- Map
- Filter
- Reduce
Map
Steven alttaki tweetiyle Map
, Filter
ve Reduce
yöntemlerini çok güzel şekilde ifade etmiş:
Map/filter/reduce in a tweet:
— Steven Luscher (@steveluscher) June 10, 2016
map([🌽, 🐮, 🐔], cook)
=> [🍿, 🍔, 🍳]
filter([🍿, 🍔, 🍳], isVegetarian)
=> [🍿, 🍳]
reduce([🍿, 🍳], eat)
=> 💩
Cook
ismindeki fonksiyonumuz Girdi olarak[🌽, 🐮, 🐔]
alıyor ve herbirini pişirerek[🍿, 🍔, 🍳]
‘e dönüştürüyor.isVegeterian
filtresi[🍿, 🍔, 🍳]
arasından vejeteryanler için olmayanları filtreliyor.eat
reduce
fonksiyonu ise[🍿, 🍳]
alıp …
Biraz Teori
Hafifçe teorik gidelim:
Elimizde bir f(x)
fonksiyonu olsun.
1f(x) = y
x, sıcaklık birimi Celcius olsun ve f(x)
fonksiyonu, Celcius birimini
Fahrenheit birimine çeviren bir fonksiyon olduğunda f(x)
bir Map
(dönüşüm)
fonksiyonu diyebiliriz.
1f(°C) = °F
C# Map
Kullanımı
Map
fonksiyonunun C# dilindeki karşılığı IEnumerable
extension metodu olan
Select
fonksiyonudur. Bu yazımızda Select
fonksiyonunu kullanmayacağız,
benzerini kendimiz geliştireceğiz.
1IEnumerable<TOut> map<T, TOut>(IEnumerable<T> source, Func<T, TOut> func) {
2 foreach (var element in source)
3 yield return func(element);
4}
Celcius ↔ Fahrenheit Dönüşüm Fonksiyonu
Celcius ↔ Fahrenheit dönüşüm fonksiyonundan yola çıkarak elimizdeki fonksiyonun bir adet girdisi ve bir adet çıktısı olmalı. Alttaki gibi dönüşüm fonksiyonumuz olduğunu farz edelim.
1Fahrenheit convertCtoF (Celcius value) => value * 1.8 + 32.0;
C# Delegelerini Kullanarak Kendi Map
Fonksiyonumuzu Yazalım
Önceki yazılarımızda Func<T, TResult>
delegesinden bahsetmiştik. Bu dönüşüm
fonksiyonumuzu delege ile temsil etmek istersek:
1Func<Celcius, Fahrenheit> convertor = convertCtoF;
Elimizdeki bir Celcius veri setini, Fahrenheit değerlerine çevirecek bir fonksiyon yazalım ve bunu delegeleri kullanarak yapalım.
Alternatif #1
1Fahrenheit convertCtoF (Celcius value) => value * 1.8 + 32.0;
2
3IEnumerable<Fahrenheit> fahrenheit(IEnumerable<Celcius> values) {
4 foreach(var celcius in values) {
5 yield return convertCtoF(celcius);
6 // ^^^^^^^^^^^
7 }
8}
Buradaki yaklaşım fonksiyonel programlama yöntemlerine uygun olmadı:
fahrenheit
fonksiyonu, parametreleri dışındaglobal
olan dış bir değişkene (convertCtoF
) bağımlı. saf (pure
) değil çünkü içeride kullanılan değerler parametrelerden alınmamış. Diğer ifadeylehardcoded
bir değer.convert
fonksiyonuna direkt bağımlılık mevcut. Diğer ifadeyletightly coupled
convertCtoF
fonksiyonu buraya parametre olara geçilebilmeliydi.
Çözüm, fonksiyonları delege olarak tanımlayarak low-level
convertCtoF
fonksiyonumuzu high levelfahrenheit
fonksiyonumuza parametre olarak geçmek.
Alternatif #2
1IEnumerable<Fahrenheit> fahrenheit(IEnumerable<Celcius> values, Func<Celcius, Fahrenheit> convertor) {
2 foreach(var celcius in values)
3 yield return convertor(celcius);
4}
Yeni fonksiyonumuza Func<Celcius, Fahrenheit>
delege tipinde yeni bir parametre ekledik.Böylece bu delege tipine uyumlu herhangi bir fonksiyonu parametre olarak geçebileceğiz.
Kullanımı:
1Func<Celcius, Fahrenheit> convertor = convertCtoF;
2var fahrenheitValues = fahrenheit(celciusValues, convertor);
Genel amaçlı bir map
fonksiyonu geliştirelim
Genel amaçlı bir map
fonksiyonunu geliştirebilmek için elimizdeki hardcoded
olan tip parametrelerini jenerik hale getirmemiz gerekli.
1IEnumerable<TResult> map<T, TResult>(IEnumerable<T> values, Func<T, TResult> convertor) {
2 foreach(var value in values)
3 yield return convertor(value);
4}
Kullanımı:
1Func<Celcius, Fahrenheit> convertor = convertCtoF;
2var fahrenheitValues = map<Celcius, Fahrenheit>(celciusValues, convertor);
3
4// Type parametreleri olmadan da çağırabiliriz.
5var fahrenheitValues = map<Celcius, Fahrenheit>(celciusValues, convertor);
Yeni map
fonksiyonumuzla beraber sıcaklık değer listelerini, diğer birimlere
çevirecek fonksiyonları tek tek yazmak yerine map<T, TResult>
fonksiyonunu
kullanarak türetebileceğiz.
Örnek:
1var celciusValues = new List<Celcius>() { -40, 0, 100 };
2
3var celciusToFahrenheitValues = map (celciusValues, convertCtoF);
4
5var celciusToKelvinValues = map<Celcius, Kelvin> (new List<Celcius>() { -273, 0, 100 }, value => value + 273.0);
6
7var fahrenheitToCelciusValues = map<Fahrenheit, Celcius> (celciusToFahrenheitValues, value => (value - 32) / 1.8);
Sonuç
Bu yazımızda fonksiyon delegelerini kullanarak genel amaçlı bir map
implementasyonu yazmaya çalıştık.
Siz de bir code kata
1 uygulaması olarak ÖTV ve KDV hesaplayan fonksiyonlar yazabilir, bu fonksiyonları delege yardımıyla map
fonksiyonu aracılığıyla kullanabilirsiniz.
Örnek bir uygulama içeren LINQPad dosyasını ekte bulabilirsiniz.
Edit
- 2020/12/20 - İş arkadaşım Zişan, LINQ Pad dosyası örneği yerine dotnet fiddle önerdi. Linki aşağıya bırakıyorum, kaydırarak açabilirsiniz.