Məzmuna keç
Educora
İrəli22 dəq19 / 27

Funksional proqramlaşdırma

Proqnozlaşdırıla bilən kod yaz: təmiz funksiyalar, dəyişməzlik, yüksək tərtibli funksiyalar, karrinq, kompozisiya və güclü `reduce` şablonları.

Özünü yoxla
Bu dərsdə öyrənəcəksən
  • Təmiz funksiyanı təmiz olmayandan ayırmaq, yan təsirlərdən və verilənləri dəyişməkdən qaçmaq
  • Massiv və obyektləri dəyişmədən yeniləmək: spread, map, toSorted, structuredClone
  • Yüksək tərtibli funksiyalar, karrinq və kompozisiya (pipe/compose) ilə kiçik təkrar istifadə olunan funksiyalar qurmaq
  • Verilənləri qruplaşdırmaq, saymaq və indeksləmək üçün reduce-dan istifadə etmək

Xəta haqqında şikayət gəlir: «Səbətdəki cəm bəzən səhv olur». Bir saat axtarandan sonra tapırsan ki, hansısa funksiya ona ötürülən massivi səssizcə dəyişib. Funksional proqramlaşdırma (FP) belə səhvləri nadir edən üslubdur: funksiyalar yalnız hesablayır və nəticə qaytarır, verilənlər isə xəbərin olmadan dəyişmir. JavaScript təmiz funksional dil deyil, amma map, filter, reduce, React və Redux məhz bu ideyalar üzərində qurulub.

Təmiz funksiyalar və yan təsirlər

Tərif
Təmiz funksiya

1) Eyni arqumentlər üçün həmişə eyni nəticə qaytarır; 2) yan təsiri yoxdur: xarici dəyişənləri və arqumentlərini dəyişmir, ekrana çap etmir, fayla və ya şəbəkəyə yazmır.

Yan təsirlərsiz proqram yoxdur — nəticəni nə vaxtsa göstərmək və ya saxlamaq lazımdır. FP onları proqramın kənarlarına çəkir: nüvə təmiz funksiyalardan ibarətdir və asan test olunur, giriş-çıxış isə bir neçə aydın yerdə baş verir.

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));
▸ Gözlənilən nəticə
[ 'tea' ] [ 'tea', 'bread' ]
[ 'tea', 'jam' ]
118 118
118 120
Təmiz olmayan funksiyanın nəticəsi xarici taxRate-dən asılıdır və eyni arqumentlə dəyişdi. addItemImpure isə orijinal səbəti pozdu.

Dəyişməzlik

Dəyişməzlik o deməkdir ki, verilənləri dəyişmək əvəzinə dəyişiklik olan yeni versiya yaradırsan. Massiv və obyektlər üçün spread işlədilir, amma o, yalnız bir səviyyəni köçürür. İç-içə obyektdə dəyişdiyin hər səviyyəni ayrıca köçür, tam dərin surət üçün isə structuredClone var. Object.freeze(obj) obyekti dəyişikliklərdən qoruyur, amma o da yalnız birinci səviyyədə işləyir.

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);
▸ Gözlənilən nəticə
Baku Ganja
[ 'student' ] [ 'student', 'volunteer' ]
Shaki
Shaki Lankaran
[ 95, 82, 70 ] [ 70, 95, 82 ]
shallow yalnız xarici obyekti köçürdü, address isə ortaq qaldı — ona görə orijinal da «Shaki» oldu. structuredClone hər səviyyəni köçürür.

Yüksək tərtibli funksiyalar, karrinq və kompozisiya

Yüksək tərtibli funksiya arqument kimi funksiya qəbul edir və ya funksiya qaytarır: map, filter, setTimeout — hamısı belədir. Karrinq (currying) f(a, b) funksiyasını f(a)(b) şəklinə salır: arqumentləri hissə-hissə vermək və hazır köməkçilər düzəltmək olur. Kompozisiya kiçik funksiyaları konveyerə birləşdirir: pipe(f, g, h)(x) ifadəsi h(g(f(x))) ilə eynidir.

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));
▸ Gözlənilən nəticə
16.8
[ 16.8, 34.5, 122.99 ]
16.8
pipe funksiyaları soldan sağa, compose isə riyaziyyatdakı kimi sağdan sola tətbiq edir. round2 kəsr xətalarını gizlədir: 10 * 1.18 əslində 11.799999999999999 verir.

reduce şablonları

reduce massivdən istənilən quruluş düzəldə bilir: cəm, sayğac, qruplar, id-yə görə indeks, Map. Həmişə başlanğıc qiymət ver — boş massivdə başlanğıc qiymətsiz reduce xəta atır. Qruplaşdırma üçün müasir brauzerlərdə hazır Object.groupBy (ES2024) metodu da var.

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);
▸ Gözlənilən nəticə
140
{ Baku: 3, Ganja: 1 }
{ '1': 'Murad', '2': 'Leyla', '3': 'Murad', '4': 'Elvin' }
Map(3) { 'Murad' => 55, 'Leyla' => 25, 'Elvin' => 60 }
Tapşırıq

Təmiz applyDiscount(products, percent) funksiyasını yaz: o, endirimli qiymətləri olan yeni massiv qaytarsın, products isə dəyişməsin. Yeni qiymət: price * (100 - percent) / 100.

Tapşırıq · 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);
▸ Gözlənilən nəticə
[ { name: 'Pen', price: 1.6 }, { name: 'Notebook', price: 4 } ]
[ { name: 'Pen', price: 2 }, { name: 'Notebook', price: 5 } ]
Tapşırıq

Kiçik funksiyalardan pipe ilə slugify qur: mətni kənar boşluqlardan təmizlə, kiçik hərflərə çevir, sözlərə böl (boş hissələri at) və sözləri - ilə birləşdir.

Tapşırıq · 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'));
▸ Gözlənilən nəticə
learn-javascript-today
functional-programming

Əsas fikirlər

  • Təmiz funksiya yalnız arqumentlərindən asılıdır və yan təsiri yoxdur — onu test etmək və təkrar istifadə etmək asandır.
  • Aldığın verilənləri dəyişmə: spread, map, filter, toSorted ilə yeni massiv və obyektlər yarat.
  • Spread səthi surət çıxarır; dəyişdiyin hər iç səviyyəni köçür və ya structuredClone işlət.
  • Yüksək tərtibli funksiyalar funksiya alır və ya qaytarır; karrinq arqumentləri əvvəlcədən doldurur, pipe/compose kiçik funksiyaları birləşdirir.
  • Başlanğıc qiyməti olan reduce verilənləri cəmləyir, sayır, qruplaşdırır və indeksləyir.

Özünü yoxla

10 sual. Hər düzgün cavab XP qazandırır.

1 / 10
Hansı funksiya təmizdir?