- Saf fonksiyonları saf olmayanlardan ayırt etmek, yan etkilerden ve veriyi değiştirmekten kaçınmak
- Dizileri ve nesneleri değiştirmeden güncellemek: spread,
map,toSorted,structuredClone - Yüksek mertebeden fonksiyonlar, currying ve bileşim (
pipe/compose) ile küçük yeniden kullanılabilir fonksiyonlar kurmak - Verileri gruplamak, saymak ve indekslemek için
reducekullanmak
Bir hata bildirimi geliyor: “Sepet toplamı bazen yanlış çıkıyor.” Bir saatlik aramadan sonra kendisine verilen diziyi sessizce değiştiren bir fonksiyon buluyorsun. Fonksiyonel programlama (FP) bu tür hataları nadir kılan bir üsluptur: fonksiyonlar yalnızca hesaplar ve sonuç döndürür, veriler de senden habersiz değişmez. JavaScript saf fonksiyonel bir dil değildir, ama map, filter, reduce, React ve Redux bu fikirler üzerine kuruludur.
Saf fonksiyonlar ve yan etkiler
1) Aynı argümanlar için her zaman aynı sonucu döndürür; 2) yan etkisi yoktur: dış değişkenleri ve argümanlarını değiştirmez, ekrana yazdırmaz, dosyaya ya da ağa yazmaz.
Yan etkisi olmayan program yoktur; sonucu bir noktada göstermen ya da kaydetmen gerekir. FP onları programın kenarlarına iter: çekirdek, test etmesi kolay saf fonksiyonlardan oluşur, girdi/çıktı ise birkaç net yerde gerçekleşir.
let taxRate = 0.18;
const priceWithTaxImpure = (price) => price + price * taxRate;
const priceWithTax = (price, rate) => price + price * rate;
function addItemImpure(cart, item) {
cart.push(item);
return cart;
}
const addItem = (cart, item) => [...cart, item];
const cart = ['tea'];
const newCart = addItem(cart, 'bread');
console.log(cart, newCart);
addItemImpure(cart, 'jam');
console.log(cart);
console.log(priceWithTax(100, 0.18), priceWithTaxImpure(100));
taxRate = 0.2;
console.log(priceWithTax(100, 0.18), priceWithTaxImpure(100));▸ Beklenen çıktı
[ 'tea' ] [ 'tea', 'bread' ] [ 'tea', 'jam' ] 118 118 118 120
taxRate'e bağlıdır ve aynı argümanla değişti. addItemImpure ise orijinal sepeti bozdu.Değişmezlik
Değişmezlik, veriyi değiştirmek yerine değişikliği içeren yeni bir sürüm oluşturmak demektir. Diziler ve nesneler için spread kullanılır, ama yalnızca tek bir düzeyi kopyalar. İç içe bir nesnede değiştirdiğin her düzeyi ayrıca kopyala; tam derin kopya için structuredClone vardır. Object.freeze(obj) bir nesneyi değişikliklere karşı korur, ama o da yalnızca ilk düzeyde çalışır.
const user = { name: 'Aysel', address: { city: 'Baku' }, tags: ['student'] };
const moved = { ...user, address: { ...user.address, city: 'Ganja' } };
const tagged = { ...user, tags: [...user.tags, 'volunteer'] };
console.log(user.address.city, moved.address.city);
console.log(user.tags, tagged.tags);
const shallow = { ...user };
shallow.address.city = 'Shaki';
console.log(user.address.city);
const deep = structuredClone(user);
deep.address.city = 'Lankaran';
console.log(user.address.city, deep.address.city);
const scores = [70, 95, 82];
console.log(scores.toSorted((a, b) => b - a), scores);▸ Beklenen çıktı
Baku Ganja [ 'student' ] [ 'student', 'volunteer' ] Shaki Shaki Lankaran [ 95, 82, 70 ] [ 70, 95, 82 ]
shallow yalnızca dış nesneyi kopyaladı, address ise ortak kaldı; bu yüzden orijinal de “Shaki” oldu. structuredClone her düzeyi kopyalar.Yüksek mertebeden fonksiyonlar, currying ve bileşim
Yüksek mertebeden fonksiyon, argüman olarak fonksiyon alan ya da fonksiyon döndüren fonksiyondur: map, filter, setTimeout hep böyledir. Currying (körileme), f(a, b) fonksiyonunu f(a)(b) biçimine getirir: argümanları parça parça verebilir ve hazır yardımcılar üretebilirsin. Bileşim (composition) küçük fonksiyonları bir üretim bandına bağlar: pipe(f, g, h)(x), h(g(f(x))) ile aynıdır.
const add = (a) => (b) => a + b;
const multiply = (a) => (b) => a * b;
const addVat = multiply(1.18);
const addDelivery = add(5);
const round2 = (x) => Math.round(x * 100) / 100;
const pipe = (...fns) => (x) => fns.reduce((value, fn) => fn(value), x);
const compose = (...fns) => (x) => fns.reduceRight((value, fn) => fn(value), x);
const finalPrice = pipe(addVat, addDelivery, round2);
console.log(finalPrice(10));
console.log([10, 25, 99.99].map(finalPrice));
console.log(compose(round2, addDelivery, addVat)(10));▸ Beklenen çıktı
16.8 [ 16.8, 34.5, 122.99 ] 16.8
pipe fonksiyonları soldan sağa, compose ise matematikteki gibi sağdan sola uygular. round2 ondalık hatalarını gizler: 10 * 1.18 aslında 11.799999999999999 verir.reduce kalıpları
reduce bir diziden her türlü yapıyı kurabilir: toplam, sayaçlar, gruplar, id'ye göre dizin, bir Map. Her zaman bir başlangıç değeri ver; boş bir dizide başlangıç değeri olmayan reduce hata fırlatır. Gruplama için modern tarayıcılarda hazır Object.groupBy (ES2024) metodu da vardır.
const orders = [
{ id: 1, customer: 'Murad', city: 'Baku', total: 40 },
{ id: 2, customer: 'Leyla', city: 'Ganja', total: 25 },
{ id: 3, customer: 'Murad', city: 'Baku', total: 15 },
{ id: 4, customer: 'Elvin', city: 'Baku', total: 60 },
];
const revenue = orders.reduce((sum, order) => sum + order.total, 0);
const countByCity = orders.reduce((acc, { city }) => {
acc[city] = (acc[city] ?? 0) + 1;
return acc;
}, {});
const byId = orders.reduce((acc, order) => ({ ...acc, [order.id]: order.customer }), {});
const totalByCustomer = orders.reduce(
(map, { customer, total }) => map.set(customer, (map.get(customer) ?? 0) + total),
new Map(),
);
console.log(revenue);
console.log(countByCity);
console.log(byId);
console.log(totalByCustomer);▸ Beklenen çıktı
140
{ Baku: 3, Ganja: 1 }
{ '1': 'Murad', '2': 'Leyla', '3': 'Murad', '4': 'Elvin' }
Map(3) { 'Murad' => 55, 'Leyla' => 25, 'Elvin' => 60 }Saf bir applyDiscount(products, percent) fonksiyonu yaz: indirimli fiyatlarla yeni bir dizi döndürsün, products ise değişmesin. Yeni fiyat: price * (100 - percent) / 100.
const products = [
{ name: 'Pen', price: 2 },
{ name: 'Notebook', price: 5 },
];
function applyDiscount(products, percent) {
// return a new array; do not change products
}
console.log(applyDiscount(products, 20));
console.log(products);▸ Beklenen çıktı
[ { name: 'Pen', price: 1.6 }, { name: 'Notebook', price: 4 } ]
[ { name: 'Pen', price: 2 }, { name: 'Notebook', price: 5 } ]pipe ile küçük fonksiyonlardan slugify kur: metnin kenarlarındaki boşlukları at, küçük harfe çevir, kelimelere böl (boş parçaları at) ve kelimeleri - ile birleştir.
const pipe = (...fns) => (x) => fns.reduce((value, fn) => fn(value), x);
const trim = (text) => text.trim();
const lower = (text) => text.toLowerCase();
// write words(text) and joinWithDash(words)
const slugify = pipe(trim, lower);
console.log(slugify(' Learn JavaScript Today '));
console.log(slugify('Functional Programming'));▸ Beklenen çıktı
learn-javascript-today functional-programming
Önemli noktalar
- Saf fonksiyon yalnızca argümanlarına bağlıdır ve yan etkisi yoktur; test etmesi ve yeniden kullanması kolaydır.
- Aldığın verileri değiştirme: spread,
map,filter,toSortedile yeni diziler ve nesneler oluştur. - Spread yüzeysel bir kopya çıkarır; değiştirdiğin her iç düzeyi kopyala ya da
structuredClonekullan. - Yüksek mertebeden fonksiyonlar fonksiyon alır ya da döndürür; currying argümanları önceden doldurur,
pipe/composeküçük fonksiyonları zincirler. - Başlangıç değerli
reduceverileri toplar, sayar, gruplar ve dizinler.
Kendini test et
10 soru. Her doğru cevap XP kazandırır.