IlmHamroh
JavaScript Full-stack/12-qism. JavaScript: ilg'or mavzular va kod sifati25/44-dars19 daqiqa
Mundarija (34)

JavaScript currying va partial application: argumentlarni bittalab berish

Qisqacha: Currying — ko'p argumentli funksiyani bittadan argument oladigan funksiyalar zanjiriga aylantirish: chegirma(10, 35000) o'rniga chegirma(10)(35000). Partial application (qisman qo'llash) — argumentlarning bir qismini oldindan berib, qolganini kutadigan yangi funksiya olish: const onFoiz = chegirma(10). Ikkalasining foydasi bir: sozlangan, bir argumentli funksiyalar map, filter va pipe ga to'g'ridan-to'g'ri sig'adi.

Bu darsda

  • Currying va qisman qo'llash nima ekanini va ular bir-biridan qanday farq qilishini tushuntira olasiz.
  • Curried funksiyani arrow'lar zanjiri bilan qo'lda yozasiz.
  • Istalgan funksiyani curried qiladigan universal curry ni o'zingiz yozasiz va length tuzog'ini bilasiz.
  • Argumentlar tartibini "sozlama oldin, ma'lumot oxirida" qoidasi bilan tanlaysiz.
  • Curried funksiyalarni filter, map va pipe bilan birga ishlatasiz.

Oldin bilishingiz kerak: Kompozitsiya: compose va pipe, call, apply, bind, Closure, Closure amaliyotda.

1. Nega bu kerak?

O'tgan darsning «Ko'p uchraydigan xatolar» bo'limida bir muammo ochiq qolgan edi. chegirma(foiz, narx) ikki argument oladi, quvur esa har bosqichga bittasini beradi:

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

console.log([35000, 28000].map(chegirma)); // [ 0, -279 ]

map birinchi argumentga narxni, ikkinchisiga indeksni berdi. chegirma(35000, 0) — "35 000 foiz chegirma, narx 0" degani bo'lib qoldi. Ikkinchisi esa "28 000 foiz, narx 1" — manfiy narx. Natija ma'nosiz.

Yechimni bilamiz — arrow bilan o'rash: map((narx) => chegirma(10, narx)). Lekin «Bahor» kodida bunday o'ramlar ko'payib ketdi:

js
taomlar.filter((t) => t.narx < 30000);
taomlar.filter((t) => t.narx < 10000);
taomlar.filter((t) => t.turkum === "ichimlik");
taomlar.filter((t) => t.turkum === "suyuq");

Har qatorda bir xil fikr — "narxi N dan arzon", "turkumi X" — faqat son yoki so'z o'zgaradi. Shunday yozsak qanday bo'lardi?

js
taomlar.filter(arzonmi(30000));
taomlar.filter(turkummi("ichimlik"));

arzonmi(30000) — "30 000 dan arzonmi?" degan tayyor savol. Uni filter ga to'g'ridan-to'g'ri berasiz. Buning uchun funksiya argumentlarni bittalab qabul qilishi kerak: avval chegarani, keyin taomni. Bugungi dars shu haqida.

2. Qisman qo'llash: eslab olamiz

2.1 Ta'rif

call, apply, bind darsida qisman qo'llash (partial application) bilan tanishgan edik: funksiyaning bir qism argumentlarini hozir berib, qolganini keyinga qoldirish. Choyxonadagi "har doimgidek" o'xshatishini eslang: choy turi va shakar oldindan ma'lum, siz faqat nechta piyola ekanini aytasiz.

Ikki yo'li bor edi:

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

const onFoizBind = chegirma.bind(null, 10);
const onFoizArrow = (narx) => chegirma(10, narx);

console.log(onFoizBind(35000)); // 31500
console.log(onFoizArrow(35000)); // 31500
console.log([35000, 28000].map(onFoizArrow)); // [ 31500, 25200 ]

Ikkalasi ham bitta argument kutadigan yangi funksiya qaytardi. Endi u map ga bemalol sig'adi.

2.2 Universal qisman

O'sha darsda bind ning ichini o'zimiz yozgan edik (bogla). this bizga kerak bo'lmasa, u yanada soddalashadi:

js
const qisman = (funksiya, ...oldingi) =>
  (...keyingi) => funksiya(...oldingi, ...keyingi);

const chekYoz = (nom, soni, narx) =>
  `${nom} x${soni}: ${soni * narx}`;

const oshChek = qisman(chekYoz, "Osh");
const ikkiOsh = qisman(chekYoz, "Osh", 2);

console.log(oshChek(3, 35000)); // Osh x3: 105000
console.log(ikkiOsh(35000)); // Osh x2: 70000

Qisman qo'llashning asosiy belgisi: argumentlar bir yoki bir necha bo'lib beriladi, lekin ikki bosqichda. Oldindan bergansiz — qolganini bitta chaqiruvda berasiz va natija darhol chiqadi.

3. Currying

3.1 Bittalab argument

Currying — ko'p argumentli funksiyani bittadan argument oladigan funksiyalar zanjiriga aylantirish. Nomi matematik Haskell Curry sharafiga qo'yilgan. O'zbekchada maxsus nomi yo'q, dasturchilar "currying" va "curried funksiya" deyishadi.

O'xshatish: taksi ilovasida buyurtma. Avval manzilni tanlaysiz, keyin tarifni, keyin to'lov turini. Har qadamda bitta tanlov, va har qadamdan keyin ilova keyingi savolni beradi. Uchinchi tanlovdan keyingina mashina chaqiriladi.

Curried funksiyani arrow'lar bilan qo'lda yozish juda qisqa:

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

console.log(chegirma(10)(35000)); // 31500

const onFoiz = chegirma(10);
console.log([35000, 28000].map(onFoiz)); // [ 31500, 25200 ]

(foiz) => (narx) => ... ni o'ngdan o'qing: "foizni oladi va funksiya qaytaradi; u narxni oladi va natijani qaytaradi". Ikki strelka — ikki chaqiruv: chegirma(10)(35000). Birinchi qavs foizni beradi, ikkinchisi narxni.

chegirma(10) ni alohida saqlasangiz — bu qisman qo'llash bo'ldi. Curried funksiyada u bepul: bind ham, o'ram ham kerak emas.

O'zingiz hisoblang: chegirma(20)(50000) natijasi — .

3.2 Qadamma-qadam

Curried funksiya closure ustiga qurilgan. Har chaqiruv bitta argumentni "cho'ntakka" soladi va keyingi funksiyani qaytaradi. Uch argumentli misolda buni kuzating — har qadamda yangi o'zgaruvchi qanday qo'shilishiga qarang:

Diqqat qiling: chekYoz("Osh") va oshUchun(2) hech narsa hisoblamadi. Ular faqat argumentni eslab, funksiya qaytardi. Haqiqiy ish faqat oxirgi argument kelganda bajarildi.

3.3 Currying va qisman qo'llash farqi

Ikki atama ko'pincha aralashtiriladi. Farqi:

Currying Qisman qo'llash
Nima funksiyani qayta tuzish argumentni oldindan berish
Natija f(a)(b)(c) zanjiri qolganini kutuvchi funksiya
Har chaqiruvda bitta argument istalgancha

Bir gap bilan: currying — funksiyaning shakli, qisman qo'llash — uni ishlatish usuli. Curried funksiya qisman qo'llashni juda qulay qiladi: chegirma(10) — tayyor qisman qo'llangan funksiya.

flowchart LR
  A["chegirma(foiz, narx)"] -- "currying" --> B["chegirma(foiz)(narx)"]
  B -- "chegirma(10)" --> C["onFoiz(narx)"]
  A -- "bind / qisman" --> C
  C -- "onFoiz(35000)" --> D["31500"]

Diagrammada ikki yo'l bir joyga — bir argumentli onFoiz ga olib keladi. Yuqoridagisi avval funksiyani curried qiladi, pastdagisi argumentni to'g'ridan-to'g'ri bog'laydi.

Tekshirib ko'ring: const qosh = (a) => (b) => a + b; — qosh(2) nima qaytaradi? qosh(2)(3) -chi?

Javob

qosh(2) — funksiya: b ni kutadigan, a = 2 ni eslab qolgan arrow. Konsolda [Function (anonymous)] ko'rinadi. qosh(2)(3) — 5: ikkinchi qavs o'sha funksiyani 3 bilan chaqirdi.

4. Universal curry

4.1 Har funksiyani qo'lda qayta yozmaslik

Har funksiyani (a) => (b) => ... qilib qayta yozish noqulay. Ayniqsa, funksiya allaqachon yozilgan bo'lsa yoki uni oddiy usulda ham chaqirish kerak bo'lsa. Shuning uchun istalgan funksiyani curried qiladigan yordamchi yoziladi.

U qanday biladi, qachon to'xtash kerak? Funksiyaning length xususiyatidan — u nechta parametr e'lon qilinganini aytadi. Buni call, apply, bind darsida bind misolida ko'rgan edik:

js
function curry(funksiya, soni = funksiya.length) {
  return function yigish(...berilgan) {
    if (berilgan.length >= soni) {
      return funksiya(...berilgan);
    }
    return (...keyingi) => yigish(...berilgan, ...keyingi);
  };
}

const chekYoz = curry(
  (nom, soni, narx) => `${nom} x${soni}: ${soni * narx}`,
);

console.log(chekYoz("Osh")(2)(35000)); // Osh x2: 70000
console.log(chekYoz("Osh", 2)(35000)); // Osh x2: 70000
console.log(chekYoz("Osh", 2, 35000)); // Osh x2: 70000

Kodni qatorma-qator o'qiymiz:

  1. soni = funksiya.length — nechta argument kerakligi. Standart qiymat: funksiyaning parametrlar soni (bu yerda 3).
  2. return function yigish(...berilgan) — curry yangi funksiya qaytaradi. Unga yigish nomi berilgan, chunki u keyin o'zini shu nom bilan chaqiradi.
  3. berilgan.length >= soni — argumentlar yetarlimi? Ha bo'lsa — asl funksiya hamma argument bilan chaqiriladi va natija qaytadi.
  4. Yetmasa — yana bitta arrow qaytadi. U keyingi argumentlarni oladi va yigish ni eski va yangi argumentlar birgalikda bilan qayta chaqiradi.

Ya'ni yigish argumentlarni yig'adi. Yig'ilganlari closure da saqlanadi. yigish o'zini uzunroq ro'yxat bilan chaqiradi — bu rekursiya. To'xtash sharti — 3-qadam.

Qadamlarda berilgan ro'yxati qanday to'lishini kuzating:

E'tibor bering: bunday curry dan keyin funksiyani har qanday bo'laklarda chaqirsa bo'ladi — bittalab ham, ikkita-bitta ham, hammasi birdan ham. Lodash va Ramda'dagi curry ham shunday ishlaydi.

4.2 length tuzog'i

curry length ga ishonadi. length esa har doim ham haqiqiy argument sonini bermaydi:

js
const a = (x, y) => 0;
const b = (x, y = 10) => 0;
const c = (...sonlar) => 0;

console.log(a.length, b.length, c.length); // 2 1 0

Standart qiymatli parametr va undan keyingilari, shuningdek rest parametr length ga kirmaydi. Bunday funksiyani curry qilsangiz, u kutganingizdan erta "yonadi":

js
function curry(funksiya, soni = funksiya.length) {
  return function yigish(...berilgan) {
    if (berilgan.length >= soni) {
      return funksiya(...berilgan);
    }
    return (...keyingi) => yigish(...berilgan, ...keyingi);
  };
}

const xizmatli = curry(
  (narx, foiz = 10) => narx + (narx * foiz) / 100,
);
console.log(xizmatli(35000)); // 38500

const jamla = curry((...sonlar) =>
  sonlar.reduce((s, x) => s + x, 0),
);
console.log(jamla(1)); // 1

const jamlaUch = curry(
  (...sonlar) => sonlar.reduce((s, x) => s + x, 0),
  3,
);
console.log(jamlaUch(1)(2)(3)); // 6

xizmatli ning length i 1, shuning uchun u birinchi argumentdayoq bajarildi — bu yerda bu hatto qulay. jamla esa length 0 bo'lgani uchun darhol ishladi va 1 qaytardi. Yechim — sonni aniq berish: curry(funksiya, 3). Bizning curry ning ikkinchi parametri shu uchun bor.

Tekshirib ko'ring: curry((a, b, c) => a + b + c) bilan yasalgan f uchun f(1)(2) nima qaytaradi?

Javob

Funksiya. Ikki argument yig'ildi, uchtasi kerak — yigish yana bitta argument kutadigan funksiya qaytaradi. f(1)(2)(3) yoki f(1)(2, 3) — 6.

5. Argument tartibi: sozlama oldin, ma'lumot oxirida

5.1 Qaysi argument birinchi?

Currying faqat boshdagi argumentlarni oldindan berishga imkon beradi. Shuning uchun argumentlar tartibi hal qiluvchi. Ikki variantni solishtiring:

js
const arzonmiA = (taom) => (chegara) => taom.narx < chegara;
const arzonmiB = (chegara) => (taom) => taom.narx < chegara;

arzonmiA(taom) — "shu taom uchun chegara so'ra". Bu bilan filter ga hech narsa berib bo'lmaydi: bizga taom oldindan ma'lum emas, u filter ichida keladi. arzonmiB(30000) esa — "30 000 chegarasi bilan taom so'ra". Mana shu filter ga sig'adi.

Qoida: sozlama oldin, ma'lumot oxirida. Kamroq o'zgaradigan argument — chegara, foiz, turkum — birinchi. Har chaqiruvda o'zgaradigan asosiy ma'lumot — taom, narx, ro'yxat — oxirgi. Inglizcha "data-last" deyiladi. Ramda kutubxonasidagi hamma funksiyalar shu tartibda yozilgan.

5.2 «Bahor» menyusi

Endi «Nega bu kerak?» bo'limidagi orzuni amalga oshiramiz:

js
const arzonmi = (chegara) => (taom) => taom.narx < chegara;
const turkummi = (turkum) => (taom) => taom.turkum === turkum;

const taomlar = [
  { nom: "Osh", narx: 35000, turkum: "milliy" },
  { nom: "Manti", narx: 30000, turkum: "milliy" },
  { nom: "Ko'k choy", narx: 5000, turkum: "ichimlik" },
  { nom: "Kompot", narx: 8000, turkum: "ichimlik" },
];

const nomi = (taom) => taom.nom;

console.log(taomlar.filter(arzonmi(10000)).map(nomi));
console.log(taomlar.filter(turkummi("milliy")).map(nomi));

Konsolda:

text
[ "Ko'k choy", 'Kompot' ]
[ 'Osh', 'Manti' ]

filter(arzonmi(10000)) gap kabi o'qiladi: "10 000 dan arzonlarini tanla". Predikat nomi kurs qoidasiga mos — "-mi" bilan. Chegara o'zgarsa, faqat son o'zgaradi.

5.3 Curried bosqichlar quvurda

Currying va pipe bir-birini to'ldiradi. Massiv metodlarini ham curried qilsak, ular quvurning bosqichiga aylanadi:

js
const pipe = (...bosqichlar) => (kirish) =>
  bosqichlar.reduce((natija, bosqich) => bosqich(natija), kirish);

const filtrla = (shart) => (royxat) => royxat.filter(shart);
const aylantir = (fn) => (royxat) => royxat.map(fn);
const chegirma = (foiz) => (narx) => narx - (narx * foiz) / 100;

const ichimlikNarxlari = pipe(
  filtrla((t) => t.turkum === "ichimlik"),
  aylantir((t) => t.narx),
  aylantir(chegirma(20)),
);

const taomlar = [
  { nom: "Osh", narx: 35000, turkum: "milliy" },
  { nom: "Ko'k choy", narx: 5000, turkum: "ichimlik" },
  { nom: "Kompot", narx: 8000, turkum: "ichimlik" },
];

console.log(ichimlikNarxlari(taomlar)); // [ 4000, 6400 ]

filtrla — filter ning curried, data-last nusxasi: avval shart, oxirida ro'yxat. Quvur har bosqichga ro'yxatni beradi, shart esa oldindan "yopishtirilgan". O'tgan darsdagi ikki argumentli funksiya muammosi shu tarzda hal bo'ladi.

Tekshirib ko'ring: Nega JavaScript'ning o'z royxat.filter(shart) metodi bilan "data-last" haqida gapirish ma'nosiz?

Javob

Chunki metodda ma'lumot — royxat — argument emas, nuqtadan oldingi obyekt. U har doim "birinchi" keladi. Currying va data-last tartib oddiy funksiyalar uchun; metodni quvurga qo'yish uchun uni filtrla kabi funksiyaga o'raymiz.

6. Amaliy foydasi

6.1 Sozlangan funksiyalar oilasi

Bitta umumiy funksiyadan bir nechta aniq funksiya yasash — currying'ning eng ko'p ishlatiladigan joyi. Masalan, log yozuvchi:

js
const logYoz = (daraja) => (manba) => (xabar) =>
  console.log(`[${daraja}] ${manba}: ${xabar}`);

const bronXatosi = logYoz("XATO")("bron");
const bronHaqida = logYoz("INFO")("bron");

bronHaqida("Dilshod aka, 4 kishi, 19:00");
bronXatosi("Soat 23:30 — ish vaqtidan tashqari");

Konsolda:

text
[INFO] bron: Dilshod aka, 4 kishi, 19:00
[XATO] bron: Soat 23:30 — ish vaqtidan tashqari

Daraja va manba bir marta beriladi. Kodning qolgan qismi faqat bronXatosi("...") deb yozadi — qisqa va xato qilish qiyin.

6.2 Forma tekshiruvi qoidalari

Xato strategiyasi darsidagi bron tekshiruvini eslang. Qoidalarni curried funksiyalar bilan yozsak, ular qayta ishlatiladigan "g'ishtlar"ga aylanadi:

js
const kamida = (soni) => (matn) => matn.trim().length >= soni;
const oraliqda = (min, max) => (son) => son >= min && son <= max;

const ismTogrimi = kamida(2);
const odamTogrimi = oraliqda(1, 20);

console.log(ismTogrimi("Ali")); // true
console.log(ismTogrimi(" A ")); // false
console.log(odamTogrimi(25)); // false

oraliqda(1, 20) ga e'tibor bering: birinchi chaqiruv ikkita argument oldi. Currying qat'iy qoida emas — sozlamalar bir guruh bo'lsa, ularni birga berish mumkin. Muhimi — ma'lumot (son) alohida va oxirida.

6.3 Hodisa tinglovchisi fabrikasi

Brauzerda ham currying uchraydi. Bir nechta tugmaga deyarli bir xil tinglovchi kerak bo'lsa:

js
// faolTugma, hammasiTugma — sahifadagi ikki tugma
const filtrniTanla = (filtr) => (event) => {
  event.preventDefault();
  console.log(`Filtr: ${filtr}`);
};

faolTugma.addEventListener("click", filtrniTanla("faol"));
hammasiTugma.addEventListener("click", filtrniTanla("hammasi"));

filtrniTanla("faol") — hodisani kutadigan tayyor tinglovchi. Bu Closure amaliyotda darsidagi fabrika funksiyaning o'zi, faqat endi unga nom berdik — curried funksiya.

7. Qachon currying kerak emas

Currying — vosita, maqsad emas. Uni hamma joyga qo'yish kodni og'irlashtiradi:

  • Bir marta ishlatiladigan funksiya. Agar funksiya doim hamma argumentlari bilan bir joyda chaqirilsa, f(a)(b)(c) faqat qavslarni ko'paytiradi.
  • Ko'p, teng huquqli argumentlar. Beshta parametrli funksiyani bittalab chaqirish o'qishga qiyin. Bunday holatda bitta sozlamalar obyekti ({ narx, foiz, valyuta }) yaxshiroq (Parametrda destructuring va options obyekti).
  • Jamoa tanish bo'lmasa. logYoz("XATO")("bron")("...") ni birinchi ko'rgan dasturchi hayron qoladi. Yangi jamoada avval kelishib oling.
  • Stek izi. Xato chiqqanda stek izida (Stack trace) nomsiz arrow'lar zanjiri ko'rinadi. Nomli qadamlar bilan yozilgan kodni tuzatish osonroq.

Tezlik haqida qayg'urmang: har chaqiruv kichik funksiya yaratadi, lekin odatdagi ilovada bu sezilmaydi. Muammo bo'lsa — o'lchab ko'ring (Performansni o'lchash darsida).

8. Ko'p uchraydigan xatolar

8.1 Qo'lda curried funksiyani bir chaqiruvda chaqirish

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

console.log(chegirma(10, 35000)); // [Function (anonymous)]

Xato xabari yo'q — faqat natija o'rnida funksiya. Qo'lda yozilgan curried funksiya ikkinchi argumentni jimgina tashlab yuboradi. Tuzatish: chegirma(10)(35000). Yoki curry yordamchisi bilan yasang — u ikkala usulni ham qabul qiladi.

8.2 Oxirgi qavsni unutish

const narx = chegirma(10); deb yozib, narx ni son deb o'ylash. Keyin unga son qo'shilsa, g'alati satr chiqadi:

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

console.log(narx + 1000); // (narx) => narx - (narx * foiz) / 1001000

G'alati ko'rinadi, keling sekin ko'ramiz. narx — son emas, funksiya. + funksiyani satrga — o'zining kod matniga — aylantirdi: (narx) => narx - (narx * foiz) / 100. Keyin oxiriga "1000" yopishtirdi. Natijada matn oxiridagi 100 va 1000 qo'shilib, 1001000 bo'lib ko'rindi. Tuzatish: natija o'rnida [Function ...] yoki funksiya matni ko'rsangiz, qaysidir qavs yetishmayapti.

8.3 Noto'g'ri argument tartibi

Ma'lumotni birinchi qo'yish: (taom) => (chegara) => .... Bunday funksiyani filter ga oldindan sozlab bo'lmaydi. Tuzatish: sozlama oldin, ma'lumot oxirida.

8.4 curry va standart qiymat

curry((narx, foiz = 10) => ...) dan xizmatli(35000)(20) kutish. Funksiya birinchi argumentdayoq bajarildi va son qaytardi. Ikkinchi qavs esa sonni chaqirishga urinadi:

js
function curry(funksiya, soni = funksiya.length) {
  return function yigish(...berilgan) {
    if (berilgan.length >= soni) {
      return funksiya(...berilgan);
    }
    return (...keyingi) => yigish(...berilgan, ...keyingi);
  };
}

const xizmatli = curry(
  (narx, foiz = 10) => narx + (narx * foiz) / 100,
);
console.log(xizmatli(35000)(20));
text
TypeError: xizmatli(...) is not a function

Tarjimasi: "xizmatli(...) funksiya emas." xizmatli(35000) allaqachon 38500 sonini qaytardi, uni (20) bilan chaqirib bo'lmaydi. Tuzatish: sonni aniq bering — curry(fn, 2) — yoki standart qiymatdan voz keching.

9. Mashqlar

1-mashq (oson): Ko'paytiruvchi

Curried kopaytir funksiyasini qo'lda (arrow'lar bilan) yozing. Undan ikkiBaravar va uchBaravar funksiyalarini yasang va [1, 2, 3] massiviga map bilan qo'llang.

Yechim
js
const kopaytir = (a) => (b) => a * b;

const ikkiBaravar = kopaytir(2);
const uchBaravar = kopaytir(3);

console.log([1, 2, 3].map(ikkiBaravar)); // [ 2, 4, 6 ]
console.log([1, 2, 3].map(uchBaravar)); // [ 3, 6, 9 ]

kopaytir(2) — a = 2 ni eslab qolgan funksiya. map unga elementni beradi va ko'paytma qaytadi. map ning ortiqcha argumentlari (indeks, massiv) zarar qilmaydi: ichki arrow faqat bitta parametr oladi.

2-mashq (o'rta): Taksi narxi

Taksi narxi: boshlangich + km * kmNarxi. taksiNarxi ni universal curry bilan yasang (curry ni darsdan ko'chiring). Undan ekonom (boshlang'ich 5 000, km uchun 2 000) va komfort (8 000 va 3 000) tariflarini qisman qo'llab, 4 km va 10 km safarlar narxini chiqaring. Argument tartibi: avval tarif sozlamalari, oxirida masofa.

Yechim
js
function curry(funksiya, soni = funksiya.length) {
  return function yigish(...berilgan) {
    if (berilgan.length >= soni) {
      return funksiya(...berilgan);
    }
    return (...keyingi) => yigish(...berilgan, ...keyingi);
  };
}

const taksiNarxi = curry(
  (boshlangich, kmNarxi, km) => boshlangich + km * kmNarxi,
);

const ekonom = taksiNarxi(5000, 2000);
const komfort = taksiNarxi(8000)(3000);

console.log([4, 10].map(ekonom)); // [ 13000, 25000 ]
console.log([4, 10].map(komfort)); // [ 20000, 38000 ]

ekonom ikki sozlamani bitta chaqiruvda oldi, komfort — ikki chaqiruvda. Universal curry ikkalasini ham qabul qiladi. Masofa oxirida bo'lgani uchun tarif funksiyalari map ga to'g'ridan-to'g'ri sig'di.

3-mashq (qiyin): pipe + curried bosqichlar

Darsdagi pipe, filtrla va aylantir ni ishlating. Yana bitta curried bosqich yozing: saralash(kalit) — ro'yxatni kalit maydoni bo'yicha o'sish tartibida, nusxa bilan (toSorted) saralaydi. Quvur: milliy taomlarni tanlash → narx bo'yicha saralash → "Nom — narx" satrlariga aylantirish.

Yechim
js
const pipe = (...bosqichlar) => (kirish) =>
  bosqichlar.reduce((natija, bosqich) => bosqich(natija), kirish);

const filtrla = (shart) => (royxat) => royxat.filter(shart);
const aylantir = (fn) => (royxat) => royxat.map(fn);
const saralash = (kalit) => (royxat) =>
  royxat.toSorted((a, b) => a[kalit] - b[kalit]);

const milliyMenyu = pipe(
  filtrla((t) => t.turkum === "milliy"),
  saralash("narx"),
  aylantir((t) => `${t.nom} — ${t.narx}`),
);

const taomlar = [
  { nom: "Osh", narx: 35000, turkum: "milliy" },
  { nom: "Ko'k choy", narx: 5000, turkum: "ichimlik" },
  { nom: "Lag'mon", narx: 28000, turkum: "milliy" },
  { nom: "Manti", narx: 30000, turkum: "milliy" },
];

console.log(milliyMenyu(taomlar));

Konsolda:

text
[ "Lag'mon — 28000", 'Manti — 30000', 'Osh — 35000' ]

saralash("narx") — kalitni eslab qolgan bosqich. toSorted asl ro'yxatni o'zgartirmaydi, shuning uchun bosqich toza. a[kalit] — kvadrat qavs bilan maydonni nomi bo'yicha o'qish (Obyekt: kalit-qiymat juftliklari).

4-mashq: Vazifalar qadami — curried filtr predikati

vazifalar dagi VazifalarRoyxati.mosmi(vazifa, filtr) ikki argument oladi, shuning uchun korinadiganlar ichida arrow o'rami bor: filter((v) => VazifalarRoyxati.mosmi(v, filtr)). Kanon kodni o'zgartirmaymiz. kurs/mashqlar/12/25-curry/filtr.mjs faylida curried filtrgaMos(filtr) yozing. U mosmi bilan bir xil qoidaga amal qilsin, lekin filter ga to'g'ridan-to'g'ri berilsin. Uch filtrni sinang.

Yechim
js
const filtrgaMos = (filtr) => (vazifa) => {
  if (filtr === "faol") {
    return !vazifa.bajarildi;
  }
  if (filtr === "bajarilgan") {
    return vazifa.bajarildi;
  }
  return true;
};

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 },
];

for (const filtr of ["hammasi", "faol", "bajarilgan"]) {
  const idlar = vazifalar.filter(filtrgaMos(filtr)).map((v) => v.id);
  console.log(filtr, idlar);
}

Konsolda:

text
hammasi [ 1, 2, 3 ]
faol [ 1, 3 ]
bajarilgan [ 2 ]

Argument tartibi teskari bo'ldi: kanon mosmi(vazifa, filtr) da ma'lumot birinchi, bizda esa sozlama — filtr — birinchi. Shuning uchun filter(filtrgaMos(filtr)) o'ramsiz yozildi. Kanon nomlarni o'zgartirish — 12-qism oxiridagi refaktor darslarining ishi; hozircha bu faqat mashq.

bash
git add 12/25-curry/filtr.mjs
git commit -m "12/25: curried filtr predikati (data-last)"

10. Real ishda

  • Ramda va lodash/fp. Ramda'da hamma funksiya avtomatik curried va data-last: R.filter(R.propEq("milliy", "turkum")). Lodash'ning lodash/fp versiyasi ham shunday. Lodash'ning oddiy versiyasida esa _.curry va _.partial bor.
  • React. Ro'yxatdagi har element uchun tinglovchi: onClick={ochir(vazifa.id)} — ochir = (id) => () => .... 17-qismda ko'p uchratasiz.
  • Redux middleware. Redux'dagi middleware imzosi — store => next => action => ... — uch qavatli curried funksiya. Uni ko'rganingizda endi qo'rqmaysiz.
  • Sozlanadigan yordamchilar. Log yozuvchilar, validatsiya qoidalari, narx formatlovchilar — "bir marta sozlab, ko'p ishlat" kerak bo'lgan hamma joyda.
  • Intervyu. "curry funksiyasini yozing", "sum(1)(2)(3) qanday ishlaydi?", "currying va partial application farqi?" — frontend intervyularida tez-tez beriladi.

Xulosa

  • Currying — f(a, b, c) ni f(a)(b)(c) ga aylantirish; har chaqiruv bitta argumentni closure'da eslab, keyingi funksiyani qaytaradi.
  • Qisman qo'llash — argumentlarning bir qismini oldindan berish; bind, arrow o'ram yoki curried funksiyaning birinchi chaqiruvi bilan.
  • Qo'lda: (foiz) => (narx) => ...; universal curry — length bo'yicha argument yig'adi, standart qiymat va rest bilan sonni aniq bering.
  • Tartib: sozlama oldin, ma'lumot oxirida — shunda filter(arzonmi(10000)) va pipe bosqichlari o'ramsiz yoziladi.
  • Foydasi: sozlangan funksiyalar oilasi, validatsiya qoidalari, tinglovchi fabrikasi; ortiqcha ishlatilsa — kod og'irlashadi.

Keyingi dars: Memoization — toza funksiya natijasini keshlab, og'ir hisobni qayta qilmaslikni va keshni cheklashni o'rganamiz.

Manbalar

  • MDN: "Function.prototype.bind", "Function: length", "Closures" — developer.mozilla.org
  • Ramda hujjatlari: curry, filter — ramdajs.com/docs
  • Lodash hujjatlari: _.curry, _.partial — lodash.com/docs
  • Redux hujjatlari: "Middleware" — redux.js.org/understanding/history-and-design/middleware
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript currying va partial application: argumentlarni bittalab berish — IlmHamroh