Funksiyalarni karrilash va kompozitsiya qilish nima?
Savol
Funksiyalarni karrilash (currying) va kompozitsiya (composition) nima, ular nima uchun kerak va JavaScript'da qanday amalga oshiriladi?
Qisqa javob
Karrilash — bir nechta argumentli funksiyani, har biri bitta argument qabul qiladigan va barcha argumentlar yig'ilmaguncha keyingi funksiyani qaytaradigan funksiyalar ketma-ketligiga aylantirish. Kompozitsiya — bir nechta funksiyani bittaga birlashtirish, bunda birining natijasi keyingisining kirishiga aylanadi. Bu ikki pattern birgalikda murakkab mantiqni funksional uslubda kichik qayta ishlatiladigan qismlardan yig'ish imkonini beradi.
Batafsil javob
Karrilash. Oddiy f(a, b, c) funksiyasi barcha argumentlar bilan darhol chaqiriladi. Karrilangan versiyasi f(a)(b)(c) sifatida chaqiriladi — har bir chaqiruv closure orqali uzatilgan argumentni "eslab qoladi" va keyingisini kutayotgan yangi funksiyani qaytaradi. Karrilash quyidagilar uchun foydali:
- Qisman qo'llash (partial application) — argumentlarning bir qismini oldindan belgilash va ixtisoslashgan funksiya olish (masalan,
multiply(2)→ "ikkilantirish" funksiyasi). - Qayta ishlatish va kompozitsiya — bitta argumentli karrilangan funksiyalarni zanjir/pipeline'larga birlashtirish oson.
- Ko'proq ifodali API — masalan, sozlanadigan hodisa ishlovchilarida:
addValidator(rule)(value).
Umumiy amalga oshirish — argumentlar soni asl funksiyaning fn.length (arnostligi)ga tenglashguncha ularni yig'adigan universal curry funksiyasi, va faqat shundan keyin asl funksiyani chaqiradi.
Kompozitsiya. «Bir funksiyaning chiqishi — boshqasining kirishi» printsipi: compose(f, g, h)(x) f(g(h(x)))ga ekvivalent — matematikadagi kabi o'ngdan chapga bajarilish. pipe — xuddi shu printsip, lekin chapdan o'ngga (pipe(f, g, h)(x) = h(g(f(x)))), bu kodda ko'pincha tabiiyroq o'qiladi (gapda bo'lgani kabi amallar tartibi). Ikkala pattern ham funksiyalar massivi bo'yicha reduce orqali amalga oshiriladi.
Kompozitsiya va karrilash bir-birini yaxshi to'ldiradi: bitta "mazmunli" argumentli (ma'lumot) karrilangan funksiyalar kompozitsiya pipeline'ining bo'g'inlari sifatida ideal mos keladi, chunki zanjirdagi har bir funksiyaning aynan bitta kirishi va bitta chiqishi bor.
Kelishuvlar. Bu patternlar tozalik va qayta ishlatishni beradi, lekin:
- Debugging'ni murakkablashtiradi — kompozitsiyalar zanjiri bo'yicha stek trassirovkasi oraliq nomlangan
constlarga ega ketma-ketlikka qaraganda kamroq ko'rgazmali. - Yon effektlar bilan ehtiyotkorlikni talab qiladi — pattern toza funksiyalarni (ichida mutatsiya va I/O bo'lmagan) nazarda tutadi, aks holda bashoratlilikdan foyda yo'qoladi.
- Funksional uslubga o'rganmagan jamoalar uchun o'qilishni yomonlashtirishi mumkin — muvozanat muhim, dogmatizm emas.
Misol
// Universal curry funksiyasi
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args)
}
return (...more) => curried.apply(this, [...args, ...more])
}
}
function sum3(a, b, c) {
return a + b + c
}
const curriedSum = curry(sum3)
console.log(curriedSum(1)(2)(3)) // 6
console.log(curriedSum(1, 2)(3)) // 6 — bir nechtasini birga uzatish ham mumkin
console.log(curriedSum(1)(2, 3)) // 6
// Karrilash orqali qisman qo'llash
const multiply = curry((a, b) => a * b)
const double = multiply(2)
console.log(double(5)) // 10
console.log(double(21)) // 42
// compose: o'ngdan chapga
const compose = (...fns) => (x) => fns.reduceRight((acc, fn) => fn(acc), x)
// pipe: chapdan o'ngga
const pipe = (...fns) => (x) => fns.reduce((acc, fn) => fn(acc), x)
const trim = (s) => s.trim()
const toLowerCase = (s) => s.toLowerCase()
const removeSpaces = (s) => s.replace(/\s+/g, '-')
const slugify = pipe(trim, toLowerCase, removeSpaces)
console.log(slugify(' Hello World ')) // "hello-world"
// Real ma'lumotlarni qayta ishlash pipeline'ida karrilangan funksiyalar kompozitsiyasi
const users = [
{ name: 'Alice', age: 30, active: true },
{ name: 'Bob', age: 17, active: true },
{ name: 'Eve', age: 25, active: false },
]
const filterBy = curry((predicate, arr) => arr.filter(predicate))
const mapBy = curry((mapper, arr) => arr.map(mapper))
const getActiveAdultNames = pipe(
filterBy((u) => u.active),
filterBy((u) => u.age >= 18),
mapBy((u) => u.name)
)
console.log(getActiveAdultNames(users)) // ["Alice"]
Qo'shimcha savollar
- Karrilash qisman qo'llash (
Function.prototype.bind) bilan qanday bog'liq va undan nimasi bilan farq qiladi? curryni o'zgaruvchan arnostlikdagi (rest-parametrli) funksiyalar bilan to'g'ri ishlaydigan qilib qanday umumlashtirish mumkin?- Uzun
pipe/composezanjirlarini debug qilishda qanday qiyinchiliklar yuzaga keladi va ularni qanday yumshatish mumkin (masalan, oraliq qadamlarni nomlash)?