IlmHamroh
JavaScript Full-stack/12-qism. JavaScript: ilg'or mavzular va kod sifati22/44-dars21 daqiqa
Mundarija (33)

JavaScript'da funksional dasturlash: FP g'oyalari va deklarativ uslub

Qisqacha: Imperativ kod kompyuterga qanday qilishni qadamma-qadam aytadi: sikl, hisoblagich, if, o'zgaruvchini yangilash. Deklarativ kod esa nima kerakligini aytadi: buyurtmalar.filter(tolanganmi).map(summasi). Funksional dasturlash (FP) deklarativ uslubni bir necha g'oyaga quradi: funksiya — oddiy qiymat, funksiyalar toza, ma'lumot o'zgartirilmaydi, dastur — ma'lumot oqib o'tadigan bosqichlar zanjiri. Yon ta'sirlar esa dasturning chetiga — "qobiq"qa chiqariladi.

Bu darsda

  • Bitta masalani imperativ va deklarativ uslubda yozib, farqini tushuntira olasiz.
  • FP'ning asosiy g'oyalarini aytib bera olasiz: birinchi darajali funksiya, toza funksiya, havola shaffofligi, o'zgarmaslik, ma'lumot oqimi.
  • Kodni "funksional yadro" va "imperativ qobiq"qa ajrata olasiz.
  • Deklarativ kod qachon yaxshi va qachon oddiy sikl yaxshiroq ekanini bilasiz.

Oldin bilishingiz kerak: Pure funksiya va side effect, Metod zanjiri va ma'lumot pipeline, Closure amaliyotda, reduce.

1. Nega bu kerak?

Oldingi darsda eval va new Function bilan satrni kodga aylantirishning xavfini ko'rdik. Bugun boshqa savol: kodni qanday uslubda yozish kerak, toki u tushunarli va kam xatoli bo'lsin?

Sardor «Bahor» uchun kunlik hisobot yozdi. Unga uch narsa kerak edi: to'langan buyurtmalar tushumi, eng ko'p sotilgan taom va to'lanmagan buyurtmalar soni. Uning kodi bitta katta sikl edi: beshta let, uchta if, ichma-ich hisoblagichlar. Ishladi. Bir hafta o'tib administrator so'radi: "Choyni tushumdan chiqarib qo'y". Sardor bitta if qo'shdi — va to'lanmaganlar soni ham buzildi, chunki ikkalasi bitta siklda, bitta o'zgaruvchilar bilan bog'langan edi.

Muammo sikl qanday yozilganida emas. Muammo shundaki, uch xil savolning javobi bitta "qozon"da pishirildi. Bitta ingredientni o'zgartirsangiz — butun ovqat o'zgaradi.

Dasturchilar bunday kodni boshqacha yozishni o'ylab topishgan: har savol — alohida kichik funksiya, ma'lumot ular orqali "oqib" o'tadi, hech kim hech narsani yo'l-yo'lakay o'zgartirmaydi. Bu yondashuv funksional dasturlash deb ataladi. Siz uning qismlarini allaqachon bilasiz: toza funksiyalar (Pure funksiya), filter/map/reduce (Metod zanjiri), closure (Closure amaliyotda). Bugun ularni bitta tizimga yig'amiz.

Hayotdan o'xshatish: taksi. Haydovchiga ikki xil aytish mumkin. Birinchisi: "To'g'riga 300 metr, svetoforda chapga, ikkinchi burilishda o'ngga, ko'k darvoza oldida to'xta". Ikkinchisi: "Chilonzor, 9-kvartal, 12-uy". Birinchisi — imperativ (qanday borish). Ikkinchisi — deklarativ (qayerga kerak). Ikkinchisida yo'lni haydovchi va navigator o'zi topadi.

2. Imperativ va deklarativ

2.1 Bitta masala, ikki uslub

«Bahor»ning bugungi buyurtmalari:

js
const buyurtmalar = [
  { id: 1, taom: "Osh", narx: 35000, soni: 2, tolandi: true },
  { id: 2, taom: "Manti", narx: 30000, soni: 1, tolandi: false },
  { id: 3, taom: "Osh", narx: 35000, soni: 1, tolandi: true },
  { id: 4, taom: "Ko'k choy", narx: 5000, soni: 3, tolandi: true },
];

let tushum = 0;
for (let i = 0; i < buyurtmalar.length; i++) {
  const buyurtma = buyurtmalar[i];
  if (buyurtma.tolandi) {
    tushum = tushum + buyurtma.narx * buyurtma.soni;
  }
}

console.log(tushum); // 120000

Bu — imperativ uslub (imperative style): dastur kompyuterga buyruqlar ketma-ketligini beradi. "Hisoblagichni 0 qil. Indeksni 0 dan boshla. Indeks uzunlikdan kichik ekan — elementni ol. To'langan bo'lsa — tushumga qo'sh. Indeksni bittaga oshir." Natijani bilish uchun bu qadamlarni boshingizda birma-bir "bajarib ko'rish" kerak.

Endi xuddi shu hisob deklarativ uslubda:

js
const tolanganmi = (buyurtma) => buyurtma.tolandi;
const summasi = (buyurtma) => buyurtma.narx * buyurtma.soni;
const qoshish = (a, b) => a + b;

const tushum2 = buyurtmalar
  .filter(tolanganmi)
  .map(summasi)
  .reduce(qoshish, 0);

console.log(tushum2); // 120000

Bu — deklarativ uslub (declarative style): kod natija nima ekanini tasvirlaydi. Ovoz chiqarib o'qing: "buyurtmalardan to'langanlarini ol, har birining summasini top, hammasini qo'sh". Indeks yo'q, hisoblagich yo'q, let yo'q. Sikl ham bor, albatta — lekin u filter, map va reduce ning ichida yashiringan.

2.2 Farq qayerda

Ikki variantni uch savol bilan solishtiramiz:

Savol Imperativ Deklarativ
Nechta o'zgaruvchi o'zgaradi? tushum, i hech biri
Kodni o'qish uchun nimani kuzatish kerak? har qadamdagi holat bosqichlar nomini
Yangi shart qo'shish siklning ichiga if zanjirga bitta bosqich

Eng muhimi — uchinchi qator. Administrator "choysiz tushum" so'rasa, deklarativ variantga bitta qator qo'shiladi:

js
const choyEmasmi = (buyurtma) => buyurtma.taom !== "Ko'k choy";

const choysizTushum = buyurtmalar
  .filter(tolanganmi)
  .filter(choyEmasmi)
  .map(summasi)
  .reduce(qoshish, 0);

console.log(choysizTushum); // 105000

Mavjud bosqichlarning hech biriga tegmadik — yangisini qo'shdik. tolanganmi, summasi, qoshish — kichik, nomli, qayta ishlatiladigan bo'laklar. Ular Sardorning boshqa hisobotlarida ham ishlaydi.

2.3 Deklarativlik — daraja

Siz deklarativ tillarni allaqachon bilasiz, faqat bu nom bilan emas:

  • HTML: <button> — "bu yerda tugma bo'lsin". Uni piksellab qanday chizishni brauzer o'zi hal qiladi.
  • CSS: display: grid — "elementlar to'rda tursin". Har elementning koordinatasini siz hisoblamaysiz.
  • Regex: /\d+/g — "sonlarni top". Belgilarni birma-bir tekshiruvchi siklni regex dvigateli yozadi (RegExp asoslari).
  • SQL: SELECT ... WHERE tolandi — bazaga "nima kerak"ligini aytasiz; qanday qidirishni baza tanlaydi.

JavaScript ikkalasini ham qila oladi. Deklarativlik — "ha yoki yo'q" emas, daraja: siz qanchalik "qanday"ni yashirib, "nima"ni ko'rsatsangiz, kod shunchalik deklarativ.

Tekshirib ko'ring: const katta = narxlar.some((n) => n > 50000); — imperativmi yoki deklarativmi? Uni imperativ yozish uchun nima kerak bo'lardi?

Javob

Deklarativ: "birortasi 50 000 dan kattami?" degan savol to'g'ridan-to'g'ri yozilgan. Imperativ variant uchun let katta = false, for sikli, ichida if va topilganda katta = true; break; kerak bo'lardi — besh-olti qator va o'zgaruvchan holat.

3. FP'ning asosiy g'oyalari

Funksional dasturlash (functional programming, FP) — dasturni toza funksiyalar va ularning birikmasidan quradigan paradigma (dastur yozish uslubi va qarashlar tizimi). Boshqa paradigmalarni ham ko'rgansiz: imperativ (sikl va buyruqlar) va obyektga yo'naltirilgan dasturlash — ma'lumot va uning metodlarini klasslarga yig'ish (class sintaksisi). JavaScript ko'p paradigmali til: bitta faylda uchalasini ham ishlatish mumkin. FP — ulardan biri, qurol qutisidagi yana bir asbob.

FP beshta g'oyaga tayanadi. Hammasini qisman bilasiz — endi ularni yonma-yon qo'yamiz.

3.1 Funksiya — oddiy qiymat

Funksiya — qiymat darsidan bilasiz: JavaScript'da funksiyani o'zgaruvchiga saqlash, argument qilib berish va qaytarish mumkin. Bu xususiyat birinchi darajali funksiya (first-class function) deb ataladi. FP aynan shunga quriladi: filter(tolanganmi) — funksiyani boshqa funksiyaga berish.

Funksiyalarni hatto ma'lumot kabi ro'yxatga solish mumkin. Masalan, buyurtmani tekshiradigan qoidalar:

js
const qoidalar = [
  (b) => b.soni > 0 || "soni musbat bo'lsin",
  (b) => b.narx >= 1000 || "narx juda kichik",
  (b) => typeof b.taom === "string" || "taom nomi yo'q",
];

function xatolarniTop(buyurtma) {
  return qoidalar
    .map((qoida) => qoida(buyurtma))
    .filter((natija) => natija !== true);
}

const togri = { taom: "Osh", narx: 35000, soni: 2 };
const notogri = { taom: "Osh", narx: 500, soni: 0 };

console.log(xatolarniTop(togri)); // []
console.log(xatolarniTop(notogri));

Konsolda:

text
[]
[ "soni musbat bo'lsin", 'narx juda kichik' ]

Har qoida — kichik funksiya: to'g'ri bo'lsa true, aks holda xato matni. || operatori chap tomon yolg'on bo'lsa o'ng tomonni qaytaradi (Mantiqiy operatorlar). Yangi qoida — massivga yangi qator, xatolarniTop ga tegilmaydi.

Funksiya qaytaradigan funksiya ham shu g'oyaning qismi. Closure amaliyotda darsidagi fabrika funksiyalarni eslang:

js
const chegirmaQil = (foiz) => (narx) => narx - (narx * foiz) / 100;

const talabaChegirmasi = chegirmaQil(10);
console.log([35000, 30000].map(talabaChegirmasi)); // [ 31500, 27000 ]

chegirmaQil(10) yangi funksiya qaytardi va u to'g'ridan-to'g'ri map ga ketdi. Bunday "funksiya yasaydigan funksiyalar" — Currying darsining mavzusi.

3.2 Toza funksiya va havola shaffofligi

Pure funksiya darsidan ikki qoida: bir xil kirish — doim bir xil natija; tashqi dunyoni o'zgartirmaydi. Bu ikki qoidadan muhim natija kelib chiqadi.

summasi({ narx: 35000, soni: 2 }) doim 70000 qaytaradi va hech narsani o'zgartirmaydi. Demak, kodda bu chaqiruvni to'g'ridan-to'g'ri 70000 bilan almashtirsangiz, dastur xulqi o'zgarmaydi. Bu xususiyat havola shaffofligi (referential transparency) deb ataladi: ifoda o'z qiymati bilan almashtirilishi mumkin. Xuddi narxlar jadvali kabi: "osh narxi" o'rniga "35 000" deb yozsangiz, hisob o'zgarmaydi — chunki jadval har so'raganda bir xil javob beradi.

js
let hisoblagich = 0;
const summasi = (b) => b.narx * b.soni;
const sanabSummasi = (b) => {
  hisoblagich++;
  return b.narx * b.soni;
};

const osh = { narx: 35000, soni: 2 };
console.log(summasi(osh) + summasi(osh)); // 140000
const ikkalasi = sanabSummasi(osh) + sanabSummasi(osh);
console.log(ikkalasi, hisoblagich); // 140000 2

summasi(osh) + summasi(osh) ni 70000 + 70000 deb yozsak — hech narsa o'zgarmaydi. sanabSummasi esa natijada xuddi shu sonni beradi, lekin har chaqiruvda hisoblagich ni o'zgartiradi. Uni 70000 bilan almashtirsak, hisoblagich 0 bo'lib qoladi — xulq o'zgardi. Demak, u shaffof emas.

Nega bu muhim? Havola shaffof kodni qismlarga bo'lib o'qish mumkin. Har funksiyani alohida tushunasiz, alohida sinaysiz, natijasini eslab qolasiz (Memoization aynan shunga tayanadi). Shaffof bo'lmagan kodda esa har chaqiruv "yana nimani o'zgartirdi?" degan savol tug'diradi.

3.3 Ma'lumot o'zgartirilmaydi

FP'da funksiya kirgan ma'lumotni o'zgartirmaydi — yangisini qaytaradi. filter, map, toSorted (Nusxa bilan o'zgartirish) aynan shunday ishlaydi: asl massiv joyida qoladi. sort, push, splice esa asl massivni o'zgartiradi — FP uslubidagi zanjirga ular to'g'ri kelmaydi.

Bu g'oya shunchalik muhimki, unga keyingi butun dars bag'ishlangan — Immutability chuqur. Hozircha qoida: zanjir ichida hech narsani joyida o'zgartirmang.

3.4 Ma'lumot oqimi

Imperativ kodda ma'lumot bir joyda "turadi" va uni ko'p qadam o'zgartiradi. FP'da esa ma'lumot bosqichlardan oqib o'tadi: har bosqich kirishni oladi va yangi chiqish beradi. Bu ma'lumot oqimi (data flow) — Metod zanjiri darsidagi pipeline'ning umumiy nomi.

Sxemada tushum hisobi qanday oqishini kuzating. Har strelka ustida — o'sha joydagi qiymat:

flowchart LR
  A["buyurtmalar<br/>4 ta"] -->|"filter(tolanganmi)"| B["3 ta to'langan"]
  B -->|"map(summasi)"| C["70000, 35000, 15000"]
  C -->|"reduce(qoshish, 0)"| D["tushum<br/>120000"]

Har to'rtburchak — alohida qiymat, hech biri boshqasini buzmaydi. Xato chiqsa, u qaysi bosqichda ekanini topish oson: har oraliq natijani console.log bilan ko'rish mumkin. Bosqichlarni ulashning umumiy vositasi — pipe — Kompozitsiya darsida.

3.5 Ifoda — ko'rsatmadan afzal

Imperativ kod ko'pincha ko'rsatmalar (statements) bilan yoziladi: "o'zgaruvchi e'lon qil, keyin shartga qarab unga qiymat ber". FP esa ifodalarni afzal ko'radi: qiymatni darhol, bir joyda hisoblaydigan yozuv.

js
const buyurtma = { tolandi: false };

let holat1;
if (buyurtma.tolandi) {
  holat1 = "to'langan";
} else {
  holat1 = "kutilmoqda";
}

const holat2 = buyurtma.tolandi ? "to'langan" : "kutilmoqda";

console.log(holat1, holat2); // kutilmoqda kutilmoqda

Ikkinchi variantda holat2 — const, u bir marta, e'lon qilingan joyda qiymat oladi. Birinchisida holat1 bir muddat undefined bo'lib turadi va keyin o'zgaradi. Kichik farq, lekin katta kodda let qancha kam bo'lsa, "bu o'zgaruvchi hozir nimaga teng?" degan savol shuncha kam.

Ko'p shoxli switch ham ko'pincha qidiruv obyekti bilan almashtiriladi (switch va uning muqobillari):

js
const holatMatni = {
  yangi: "Qabul qilindi",
  tayyor: "Tayyor, olib keting",
  berildi: "Yoqimli ishtaha!",
};

console.log(holatMatni["tayyor"]); // Tayyor, olib keting
const nomalum = holatMatni["bekor"] ?? "Holat aniq emas";
console.log(nomalum); // Holat aniq emas

Bu ham deklarativ: "holat → matn" moslik ma'lumot sifatida yozilgan, shartlar ketma-ketligi sifatida emas.

Tekshirib ko'ring: const narxlar = []; for (const b of buyurtmalar) narxlar.push(b.narx); — buni bitta ifoda bilan qanday yozasiz? Natijada qaysi FP g'oyalari ishladi?

Javob

const narxlar = buyurtmalar.map((b) => b.narx);. Bo'sh massivni o'zgartirib to'ldirish (push) o'rniga, yangi massiv bitta ifodada hosil bo'ldi (o'zgarmaslik, ifoda). map ga funksiya berildi (birinchi darajali funksiya), va u toza: tashqarida hech narsani o'zgartirmaydi.

4. Deklarativ vositalar qutisi

Oldingi qismlarda o'rgangan ko'p vositalar aslida deklarativ. Ularni bitta jadvalda eslaymiz — har biri "qanday" sikl yozishdan ozod qiladi:

Savol Vosita
Shartga mos elementlar filter, find, some, every
Har elementni o'zgartirish map, flatMap
Bitta qiymatga yig'ish reduce
Guruhlarga ajratish Object.groupBy, Map.groupBy
Saralangan nusxa toSorted
Takrorsiz to'plam new Set(...), Set metodlari
Obyekt ↔ juftliklar Object.entries, Object.fromEntries

Ular birga qanday ishlashini ko'raylik. Administrator so'raydi: "Har taom bo'yicha to'langan tushum, kattasidan boshlab":

js
const buyurtmalar = [
  { taom: "Osh", narx: 35000, soni: 2, tolandi: true },
  { taom: "Manti", narx: 30000, soni: 1, tolandi: false },
  { taom: "Osh", narx: 35000, soni: 1, tolandi: true },
  { taom: "Ko'k choy", narx: 5000, soni: 3, tolandi: true },
];

const summasi = (b) => b.narx * b.soni;
const qoshish = (a, b) => a + b;

const tolanganlar = buyurtmalar.filter((b) => b.tolandi);
const guruhlar = Object.groupBy(tolanganlar, (b) => b.taom);

const hisobot = Object.entries(guruhlar)
  .map(([taom, royxat]) => ({
    taom,
    tushum: royxat.map(summasi).reduce(qoshish, 0),
  }))
  .toSorted((a, b) => b.tushum - a.tushum);

console.log(hisobot);

Konsolda:

text
[
  { taom: 'Osh', tushum: 105000 },
  { taom: "Ko'k choy", tushum: 15000 }
]

Object.groupBy buyurtmalarni taom nomi bo'yicha guruhladi. Object.entries guruhlar obyektini [taom, royxat] juftliklariga aylantirdi. map har juftlikdan kichik hisobot obyekti yasadi, toSorted — tushum bo'yicha kamayish tartibida saraladi. Bitta ham let, bitta ham push yo'q.

4.1 Deklarativ va erta chiqish

Metod zanjiri darsida zanjirning kamchiligini ko'rgan edik: u to'xtay olmaydi — "uchtasi topildi, yetadi" deya olmaydi. Har bosqich butun massivni aylanadi. O'sha darsning javobi — oddiy sikl edi.

ES2025 bu muammoni deklarativ yo'l bilan ham hal qildi: iterator helpers. Ular dangasa ishlaydi — har elementni zanjirning oxirigacha olib boradi va kerakli miqdor yig'ilishi bilan to'xtaydi:

js
const buyurtmalar = [
  { id: 1, narx: 35000 },
  { id: 2, narx: 5000 },
  { id: 3, narx: 30000 },
  { id: 4, narx: 28000 },
];

let tekshirildi = 0;
const ikkitaQimmat = buyurtmalar
  .values()
  .filter((b) => {
    tekshirildi++;
    return b.narx > 20000;
  })
  .take(2)
  .map((b) => b.id)
  .toArray();

console.log(ikkitaQimmat, tekshirildi); // [ 1, 3 ] 3

Ikkita qimmat buyurtma topilishi bilan zanjir to'xtadi — to'rtinchisi tekshirilmadi ham. Kod esa hamon "nima kerak" tilida: "qiymatlardan qimmatlarini ol, ikkitasini, id'larini".

Diqqat: filter ichidagi tekshirildi++ — yon ta'sir. Biz uni faqat nechta element tekshirilganini ko'rsatish uchun qo'ydik. Haqiqiy kodda deklarativ zanjir ichida tashqi o'zgaruvchini o'zgartirmang — bu «Ko'p uchraydigan xatolar» bo'limidagi birinchi xato.

5. Funksional yadro, imperativ qobiq

5.1 Yon ta'sirsiz dastur bo'lmaydi

Pure funksiya darsida "yon ta'sirsiz dastur foydasiz" degan edik. Ekranga chiqarish, faylga yozish, serverga so'rov, joriy vaqtni olish — hammasi yon ta'sir yoki toza bo'lmagan amal. Ularsiz dastur hech kimga hech narsa ko'rsata olmaydi.

FP ularni yo'q qilmaydi — chetga suradi. Dastur ikki qatlamga bo'linadi:

  • Funksional yadro (functional core) — barcha hisob-kitob: toza funksiyalar. Ma'lumot kiradi, natija chiqadi.
  • Imperativ qobiq (imperative shell) — yupqa tashqi qatlam: ma'lumotni oladi (fayl, tarmoq, vaqt), yadroga beradi, natijani chiqaradi.

Bu nom 2012-yilda Gary Bernhardt'ning ma'ruzasidan tarqalgan. Hayotdan o'xshatish: oshxona. Oshpaz (yadro) faqat pishiradi — masalliq kelsa, ovqat chiqaradi. U bozorga bormaydi va mehmonga o'zi olib chiqmaydi. Bozorga borish va dasturxonga tortish (qobiq) — boshqa odamlarning ishi. Shuning uchun oshpazning ishini tekshirish oson: bir xil masalliq — bir xil osh.

5.2 «Bahor» hisoboti ikki qatlamda

Sardorning kunlik hisobotini qayta quramiz. Sana ham, buyurtmalar ham yadroga parametr bo'lib kiradi — yadro ularni o'zi olmaydi:

js
// ===== Funksional yadro: faqat toza funksiyalar =====
const summasi = (b) => b.narx * b.soni;
const qoshish = (a, b) => a + b;

function hisobotYasa(buyurtmalar, sana) {
  const tolanganlar = buyurtmalar.filter((b) => b.tolandi);
  return {
    sana: sana.toISOString().slice(0, 10),
    tushum: tolanganlar.map(summasi).reduce(qoshish, 0),
    tolanmagan: buyurtmalar.length - tolanganlar.length,
  };
}

function hisobotMatni(hisobot) {
  return [
    `Sana: ${hisobot.sana}`,
    `Tushum: ${hisobot.tushum} so'm`,
    `To'lanmagan: ${hisobot.tolanmagan} ta`,
  ].join("\n");
}

// ===== Imperativ qobiq: tashqi dunyo bilan aloqa =====
const bugungi = [
  { taom: "Osh", narx: 35000, soni: 2, tolandi: true },
  { taom: "Manti", narx: 30000, soni: 1, tolandi: false },
];
const sana = new Date("2026-10-05T12:00:00Z");
console.log(hisobotMatni(hisobotYasa(bugungi, sana)));

Konsolda:

text
Sana: 2026-10-05
Tushum: 70000 so'm
To'lanmagan: 1 ta

Yadrodagi ikki funksiyada console.log ham, new Date() ham yo'q. Shuning uchun ularni istalgan sana va istalgan ro'yxat bilan, istalgan marta chaqirish mumkin — natija oldindan ma'lum. Haqiqiy dasturda qobiq new Date() ni (joriy vaqt) va bazadan buyurtmalarni oladi. Misolda esa aniq sana yozdik — natija har kompyuterda bir xil chiqishi uchun.

Sxemada ikki qatlam:

flowchart LR
  subgraph Q["Imperativ qobiq"]
    K["kirish: baza, vaqt"]
    C["chiqish: konsol, ekran"]
  end
  subgraph Y["Funksional yadro"]
    H["hisobotYasa"] --> M["hisobotMatni"]
  end
  K --> H
  M --> C

Strelkalar faqat bir yo'nalishda: qobiq yadroga ma'lumot beradi va natijani oladi. Yadro qobiqni chaqirmaydi.

Bu tuzilmaning eng katta foydasi — testlash. Yadroni sinash uchun server ham, brauzer ham kerak emas: kirish beriladi, chiqish solishtiriladi. Buni Birinchi avtomatik test darsida amalda qilasiz. React ham shu g'oyaga qurilgan: komponent — holatdan UI yasaydigan funksiya, ekranga chizish esa React'ning "qobig'i". React'ni keyingi qismlarda o'rganamiz, hozir bilish shart emas.

6. FP qayerdan keldi va qayerga boradi

FP g'oyalari kompyuterlardan ham eski. 1930-yillarda matematik Alonzo Church funksiyalar haqidagi nazariyani — lambda hisobini — yaratdi. 1958-yilda Lisp tili shu g'oyani dasturlashga olib kirdi. Keyinroq ML, Haskell, Erlang, Clojure, Elm kabi tillar paydo bo'ldi.

JavaScript'ning ijodkori Brendan Eich tilni 1995-yilda yaratayotganda Scheme (Lisp oilasidan) tilidan ilhomlangan. Shuning uchun JavaScript'da funksiyalar boshidanoq birinchi darajali bo'lgan va closure'lar bor. Bugun FP g'oyalari JavaScript ekotizimining hamma joyida:

  • React — "UI = holatdan funksiya" g'oyasi, toza komponentlar, holatni o'zgartirmasdan yangilash;
  • Redux va useReducer — reducer: (eski holat, amal) → yangi holat (immutable yangilash darsida ko'rgansiz);
  • Ramda (0.32 versiya) va lodash/fp — FP uslubidagi tayyor funksiyalar kutubxonalari.

Kutubxonalarni hozir bilish shart emas. Muhimi: FP — "faqat FP" degan dogma emas. Real JavaScript kodida klasslar, sikllar va FP yonma-yon yashaydi. Har vazifaga mos asbob tanlanadi.

Keyingi to'rt dars FP qurollarini birma-bir ochadi:

7. Qachon imperativ yaxshiroq

Halol bo'lish kerak: deklarativ kod har doim ham yaxshi emas.

  • Juda katta ma'lumot va tezlik muhim. Zanjirning har bosqichi yangi massiv yasaydi. Million element ustida oddiy sikl bir necha barobar tezroq (Metod zanjiri darsidagi o'lchov). Iterator helpers bu farqni kamaytiradi, lekin yo'q qilmaydi.
  • Murakkab qadamli mantiq. Holatlar mashinasi, qayta urinishlar, ko'p shartli break — ba'zan sikl o'qishga osonroq.
  • Haddan oshgan "aqlli" zanjir. O'nta bosqichli, ichida ichma-ich reduce bo'lgan bir qator — deklarativ, lekin tushunarsiz. Maqsad — o'qiladigan kod. Agar sikl aniqroq bo'lsa — sikl yozing.

Qoida: avval deklarativ yozing. O'lchov tezlik muammosini ko'rsatsa yoki kod tushunarsiz bo'lib qolsa — o'sha joyni imperativ qayta yozing va izoh qoldiring.

8. Ko'p uchraydigan xatolar

8.1 Zanjir ichida yon ta'sir

js
const taomlar = ["Osh", "Manti"];
const nusxa = [];

const katta = taomlar.map((taom) => {
  nusxa.push(taom);
  return taom.toUpperCase();
});

console.log(katta, nusxa); // [ 'OSH', 'MANTI' ] [ 'Osh', 'Manti' ]

Xato chiqmaydi, lekin map endi toza emas: u tashqi nusxa ni o'zgartiradi. Kod "deklarativ" ko'rinadi, aslida imperativ. Bunday yashirin o'zgarish keyin kutilmagan joyda paydo bo'ladi. Tuzatish: map faqat yangi qiymat qaytarsin. Yon ta'sir kerak bo'lsa — uni alohida, ochiq yozing (for...of yoki forEach).

8.2 map ichida return yo'q

js
const narxlar = [35000, 30000];
const yangi = narxlar.map((n) => { n * 2; });

console.log(yangi); // [ undefined, undefined ]

Figurali qavsli arrow — tanali funksiya, unda return shart (Arrow funksiyalar). Tuzatish: (n) => n * 2 yoki { return n * 2; }.

8.3 Zanjirda sort

buyurtmalar.sort(...) asl massivni o'zgartiradi — boshqa joyda shu massivdan foydalanayotgan kod kutilmagan tartibni ko'radi. Tuzatish: toSorted — u nusxa qaytaradi.

8.4 Toza funksiya ichida joriy vaqt

hisobotYasa ichida new Date() yozilsa, u har chaqiruvda boshqa natija beradi va uni sinab bo'lmaydi. Tuzatish: vaqtni parametr qilib bering — qobiq uni oladi, yadro ishlatadi.

8.5 FP'ni maqsadga aylantirish

"Bitta ham for bo'lmasin" degan qoida bilan yozilgan kod ko'pincha o'qilmaydi. Tuzatish: FP — vosita. Kod o'qiladigan va to'g'ri bo'lsin — uslub ikkinchi darajali.

9. Mashqlar

1-mashq (oson): Imperativdan deklarativga

Quyidagi kodni filter va map bilan qayta yozing. let, for va push qolmasin.

js
const taomlar = [
  { nom: "Osh", narx: 35000 },
  { nom: "Ko'k choy", narx: 5000 },
  { nom: "Manti", narx: 30000 },
];
const qimmatlar = [];
for (let i = 0; i < taomlar.length; i++) {
  if (taomlar[i].narx >= 30000) {
    qimmatlar.push(taomlar[i].nom);
  }
}
console.log(qimmatlar); // [ 'Osh', 'Manti' ]
Yechim
js
const taomlar = [
  { nom: "Osh", narx: 35000 },
  { nom: "Ko'k choy", narx: 5000 },
  { nom: "Manti", narx: 30000 },
];

const qimmatmi = (taom) => taom.narx >= 30000;
const nomi = (taom) => taom.nom;

const qimmatlar = taomlar.filter(qimmatmi).map(nomi);
console.log(qimmatlar); // [ 'Osh', 'Manti' ]

Ikki kichik nomli funksiya kodni gapga aylantirdi: "taomlardan qimmatlarini ol, nomlarini ol". qimmatmi — predikat: nomi "-mi" bilan tugaydi va true/false qaytaradi.

2-mashq (o'rta): Yadro va qobiqni ajrating

Bu funksiya bir vaqtda hisoblaydi, vaqtni oladi va konsolga yozadi. Uni ikkiga bo'ling: toza bronHolati(bron, hozir) — "o'tgan", "bugun" yoki "kelajakda" qaytarsin; qobiq esa natijani chiqarsin. Ishora: sanalarni toISOString().slice(0, 10) bilan solishtiring — satrlar "YYYY-MM-DD" shaklida bo'lgani uchun < to'g'ri ishlaydi.

js
function bronniKorsat(bron) {
  const bugun = new Date().toISOString().slice(0, 10);
  if (bron.sana < bugun) console.log(`${bron.ism}: o'tgan`);
  else if (bron.sana === bugun) console.log(`${bron.ism}: bugun`);
  else console.log(`${bron.ism}: kelajakda`);
}
Yechim
js
// Yadro
function bronHolati(bron, hozir) {
  const bugun = hozir.toISOString().slice(0, 10);
  if (bron.sana < bugun) return "o'tgan";
  return bron.sana === bugun ? "bugun" : "kelajakda";
}

// Qobiq
const bronlar = [
  { ism: "Dilshod aka", sana: "2026-10-04" },
  { ism: "Malika", sana: "2026-10-05" },
  { ism: "Jasur", sana: "2026-10-12" },
];
const hozir = new Date("2026-10-05T12:00:00Z");

for (const bron of bronlar) {
  console.log(`${bron.ism}: ${bronHolati(bron, hozir)}`);
}

Konsolda:

text
Dilshod aka: o'tgan
Malika: bugun
Jasur: kelajakda

bronHolati endi istalgan "hozir" bilan sinaladi — kelajakdagi yoki o'tgan sana bilan ham. Haqiqiy dasturda qobiq new Date() ni beradi. Diqqat: toISOString vaqtni UTC'da beradi. Toshkentda yarim tundan keyin soat 05:00 gacha UTC'da hali kecha — real loyihada mahalliy sanani olish kerak (Date va vaqt zonalari).

3-mashq (qiyin): Eng faol mehmonlar

Bronlar ro'yxatidan har mehmonning bronlar sonini hisoblang va eng ko'p bron qilgan ikkitasini "Ism: N ta" ko'rinishida qaytaring. Faqat deklarativ vositalar: Object.groupBy, Object.entries, map, toSorted, slice. Teng bo'lsa — ism bo'yicha alifbo tartibida (localeCompare).

js
const bronlar = [
  { ism: "Malika" }, { ism: "Jasur" }, { ism: "Malika" },
  { ism: "Dilshod aka" }, { ism: "Jasur" }, { ism: "Malika" },
];
console.log(engFaollar(bronlar)); // [ 'Malika: 3 ta', 'Jasur: 2 ta' ]
Yechim
js
function engFaollar(bronlar, soni = 2) {
  const guruhlar = Object.groupBy(bronlar, (bron) => bron.ism);
  return Object.entries(guruhlar)
    .map(([ism, royxat]) => ({ ism, soni: royxat.length }))
    .toSorted((a, b) => b.soni - a.soni || a.ism.localeCompare(b.ism))
    .slice(0, soni)
    .map(({ ism, soni: n }) => `${ism}: ${n} ta`);
}

const bronlar = [
  { ism: "Malika" }, { ism: "Jasur" }, { ism: "Malika" },
  { ism: "Dilshod aka" }, { ism: "Jasur" }, { ism: "Malika" },
];
console.log(engFaollar(bronlar)); // [ 'Malika: 3 ta', 'Jasur: 2 ta' ]

Taqqoslashdagi ||: sonlar farqi 0 bo'lsa (teng), ikkinchi mezon — ism ishlaydi. Oxirgi map da soni: n — destructuring'da nomini o'zgartirish, chunki soni nomi funksiya parametri bilan band. Butun funksiya — bitta ifoda, let ham, push ham yo'q.

4-mashq: Vazifalar qadami — statistika toza yadroda

Mashqlar repoda kurs/mashqlar/12/22-fp/statistika.mjs yarating. statistika(vazifalar) toza funksiyasi { jami, bajarilgan, faol, foiz } obyektini qaytarsin (foiz — bajarilganlarning butun foizi, ro'yxat bo'sh bo'lsa 0). Qobiq qismi — vazifalar ning boshlang'ich ma'lumotiga o'xshash massiv bilan uni chaqirib, natijani chiqarsin. Ishora: filter va Math.round.

Yechim
js
// Yadro
export function statistika(vazifalar) {
  const jami = vazifalar.length;
  const bajarilgan = vazifalar.filter((v) => v.bajarildi).length;
  return {
    jami,
    bajarilgan,
    faol: jami - bajarilgan,
    foiz: jami === 0 ? 0 : Math.round((bajarilgan / jami) * 100),
  };
}

// Qobiq
const vazifalar = [
  { id: 1, matn: "Non olish", bajarildi: false },
  { id: 2, matn: "DOM darsini takrorlash", bajarildi: true },
  { id: 3, matn: "Onamga qo'ng'iroq qilish", bajarildi: false },
];
console.log(statistika(vazifalar));
console.log(statistika([]));

Konsolda:

text
{ jami: 3, bajarilgan: 1, faol: 2, foiz: 33 }
{ jami: 0, bajarilgan: 0, faol: 0, foiz: 0 }

Bo'sh ro'yxat alohida tekshirildi — aks holda 0 / 0 NaN beradi. statistika faqat ma'lumot oladi va qaytaradi: uni keyinchalik vazifalar ilovasiga ham, testga ham o'zgarishsiz olib o'tish mumkin. Terminalda: node 12/22-fp/statistika.mjs.

bash
git add 12/22-fp
git commit -m "12/22: vazifalar statistikasi toza funksiyada"

10. Real ishda

  • Kod review. "Bu map nimani o'zgartiryapti?", "Bu funksiyani testda qanday chaqiraman?" — FP g'oyalari review'dagi eng ko'p savollarga javob beradi.
  • React va holat boshqaruvi. Komponentlar — holatdan UI yasaydigan funksiyalar, reducer'lar — toza funksiyalar. FP tafakkurisiz React'da qiynalasiz.
  • Ma'lumot qayta ishlash. Hisobotlar, analitika, server javoblarini formatlash — hammasi filter/map/groupBy zanjirlari bilan yoziladi.
  • Testlash. Funksional yadro — eng oson testlanadigan kod. Ko'p jamoalarda "biznes mantiq toza bo'lsin" degan qoida bor.
  • Intervyu. "Imperativ va deklarativ farqi?", "Toza funksiya nima va nega muhim?", "Havola shaffofligi nima?" — junior va middle intervyularining doimiy savollari.

Xulosa

  • Imperativ — "qanday" (sikl, hisoblagich, o'zgaruvchan holat); deklarativ — "nima" (filter(tolanganmi).map(summasi)).
  • FP g'oyalari: birinchi darajali funksiya, toza funksiya, havola shaffofligi, ma'lumotni o'zgartirmaslik, ma'lumot oqimi, ko'rsatmadan ko'ra ifoda.
  • Havola shaffof chaqiruvni o'z qiymati bilan almashtirish mumkin — shuning uchun uni alohida tushunish, sinash va keshlash oson.
  • Funksional yadro, imperativ qobiq: hisob-kitob toza, yon ta'sirlar (vaqt, konsol, tarmoq) — chetda.
  • Iterator helpers deklarativ zanjirga erta chiqishni qo'shadi; juda katta ma'lumot yoki murakkab mantiqda oddiy sikl ham to'g'ri tanlov.

Keyingi dars: Immutability chuqur — FP'ning uchinchi g'oyasi alohida: nega ma'lumotni o'zgartirmaslik kerak, Object.freeze va structuredClone qayerda yetmaydi va structural sharing uni qanday arzon qiladi.

Manbalar

  • MDN: "Functional programming" (Glossary), "First-class Function", "Iterator.prototype.take()" — developer.mozilla.org
  • Eric Elliott, "Composing Software" — FP asoslari JavaScript'da
  • Gary Bernhardt, "Boundaries" (2012) — "Functional Core, Imperative Shell" — destroyallsoftware.com
  • Kyle Simpson, "Functional-Light JavaScript" — github.com/getify/Functional-Light-JS
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript'da funksional dasturlash: FP g'oyalari va deklarativ uslub — IlmHamroh