- 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
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.
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
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.
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.
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.
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 }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.
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 } ]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.
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,toSortedilə 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
structuredCloneişlət. - Yüksək tərtibli funksiyalar funksiya alır və ya qaytarır; karrinq arqumentləri əvvəlcədən doldurur,
pipe/composekiçik funksiyaları birləşdirir. - Başlanğıc qiyməti olan
reduceverilə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.