İçeriğe geç
Educora
İleri22 dk19 / 27

Fonksiyonel programlama

Öngörülebilir kod yaz: saf fonksiyonlar, değişmezlik, yüksek mertebeden fonksiyonlar, currying, bileşim ve güçlü `reduce` kalıpları.

Kendini test et
Bu derste öğreneceklerin
  • 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 reduce kullanmak

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

Tanım
Saf fonksiyon

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.

JavaScript
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
Saf olmayan fonksiyonun sonucu dışarıdaki 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.

JavaScript
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.

JavaScript
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.

JavaScript
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 }
Alıştırma

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.

Alıştırma · JavaScript
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 } ]
Alıştırma

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.

Alıştırma · JavaScript
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, toSorted ile yeni diziler ve nesneler oluştur.
  • Spread yüzeysel bir kopya çıkarır; değiştirdiğin her iç düzeyi kopyala ya da structuredClone kullan.
  • Yüksek mertebeden fonksiyonlar fonksiyon alır ya da döndürür; currying argümanları önceden doldurur, pipe/compose küçük fonksiyonları zincirler.
  • Başlangıç değerli reduce verileri toplar, sayar, gruplar ve dizinler.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
Hangi fonksiyon saftır?