Funktsiooni koostis vs reduktor JavaScriptis

Pablo García Saldaña “Kollane liiklusmärk musta noolega, mis näitab mõlemat suunda Kuival oru lähedal kuival tee ääres”, autor Pablo García Saldaña saidil Unsplash

Funktsiooni koostis on fantastiline. Tavaliselt kirjeldan seda kui “see on modulaarseks jadaks, mida saate igal ajal mitmel viisil kasutada”. Funktsiooni komponeerimisel saate kasutada muid funktsioone ja neid funktsioone saab hiljem oma koodi teises osas kasutada - see on lihtne, kasulik ja fantastiline. Selles postituses eeldan, et teil on teadmisi ja / või kogemusi funktsionaalse programmeerimisega, kui te seda ei tee, saate sellest Eric Elliotti selles postituses teada saada.

Funktsiooni koostis

Ütleme, et mul on numbrite massiiv, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ja ma pean saama toru abil eraldatud numbrijada | mis on võrdsed ja korrutatud 10-ga, näiteks 20, 40, 60, 80 ja 100. Kuidas saab seda teha funktsiooni koostisega? Oletame, et kasutame sellist raamatukogu nagu Lodash, Ramda või meil on vajalikud funktsioonid:

konstandenumbrid = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const eventNumbersBy10 = kirjuta (
  filter (x => x% 2 === 0),
  kaart (x => x * 10),
  liitu ('|')
);
eventNumbersBy10 (numbrid);

See töötab hästi ning on selge ja modulaarne. Protseduuri on lihtne lugeda: me filtreerime massiivi paarisarvude järgi, me korrutame iga paarisarvu 10-ga ja teisendame tulemuste massiivi stringiks, eraldades numbrid toruga.

Reduktor

Kas meil on lahendus vähendamise abil? Muidugi saame. Kui meil on massiiv ja me peame sellega midagi tegema, siis saame seda teha reduktoriga, isegi kui peame pääsema juurde konkreetsele elemendile, oleks see muidugi tulemuslikult halb, kuna reduktor läbib massiivi, O (n), võrreldes juurdepääsuga elemendile O (1), kuid me saame seda teha, kui tahame. Vaatame meie eelmist näidet paarisarvude vähendamise abil:

konstandenumbrid = [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12];
numbers.reduce ((str, number) => {
  if (arv% 2 === 0) {
    tagasi str + number + '| ';
  }
  tagasi str;
}, '');

Neid kahte näidet võrreldes on funktsioonide koostis selgem, hõlpsamini loetav ja hooldatav. Reduktor näeb hea välja, kuid kui lahendusele tähelepanu pöörata, annaks see tulemuseks “2 | 4 | 6 | 8 | 10 | 12 | “. Meie lahendamiseks peame lisama täiendava rea, et kontrollida, kas stringi viimases osas on toru, näiteks järgmine:

numbers.reduce ((str, number, indeks) => {
  const newStr = (arv% 2 === 0)
    ? str + arv + '| '
    : str;
  kui (
    indeks === numbrid.pikkus - 1 &&
    newStr [newStr.length - 2] === '|'
  ) {
    newStr = newStr.substring (0, newStr.length - 3);
  }
  tagasi newStr;
}, '');

Nüüd saame sama tulemuse, kuid seda on raskem lugeda ja mõista.

Funktsioonid saavad samad parameetrid

Nüüd hakkan näidet muutma. Mul on objekt nimega data, kus mul on kogu teave kasutaja kohta:

const andmed = {
  id: '123',
  profiil: {
    eesnimi: 'Giuliano',
    perekonnanimi: 'Kranevitter',
    Sugu Mees'
  },
  mandaadid: {
    kasutajanimi: 'giulianok',
    parool: '*********'
  },
  sotsiaalmeedia: {
    twitter: 'https://twitter.com/giulianokcom',
    linkedin: 'https://www.linkedin.com/in/giulianok/'
  },
  veebisait: 'http://giulianok.com'
};

Ja me komponeerime kaks erinevat funktsiooni: getUserWithCredentialsAndProfile ja getExposableUser:

const getUserWithCredentialsAndProfile = Koosta (
  getID,
  getCredentials,
  getProfile,
);
// getUserWithCredentialsAndProfile (andmed);
const getExposableUser = kirjuta (
  getID,
  getProfile,
  getSocialMedia,
  getWebsite
);
// getExposableUser (andmed);

getID ja getProfile näitavad, kuidas saame funktsioone kompositsioonide tegemisel uuesti kasutada. Kuna funktsiooni väljundiks on järgmise sisend, peab getID tagastama uue kasutajaobjekti ja andmed, sest järgmine funktsioon getCredentials nõuab andmeid, kust ta saab kasutaja andmeid, ja uut kasutajat, mis on objekt, mida me soovime ehitama. Vaatame, kuidas getCredentials välja näeb:

const getCredentials = ({data, newUser}) => {
  tagastama {
    andmed,
    newUser: Object.assign (// Ära kunagi muteeri!
      {},
      uus kasutaja,
      {mandaadid: andmed}
    )
  }
};

See tähendab, et getID, getCredentials, getProfile, getSocialMedia ja getWebsite peavad saama sama parameetri, see on kahe atribuudiga objekt, data ja newUser. Kas saab vältida andmete edastamist igas funktsioonis igal ajal? Jah me saame! Ja sellepärast aitab reduktor meid!

Selle asemel, et funktsioon koostada ja samu parameetreid ikka ja jälle edasi anda, teame, et ainus parameeter, mida vajame, on andmed, kuna kõik funktsioonid vajavad seda, ja siis peame lihtsalt uue objekti teisendama eelmiste andmetega vaatame, kuidas see välja näeb:

const getCredentials = data => data.credentials;
const getID = data => ({id: data.id});
const getExposableUser = data => [
  getID,
  getProfile,
  getSocialMedia,
  getWebsite
] .reduce ((newUser, fn) => Object.assign ({}, newUser, fn (data)), {});

getExposableUser on funktsioon, mis võtab andmeid argumendina. Seejärel tagastab ta funktsioonide hulga ja kohe kutsume taandamist. Anonüümse redutseerimise funktsioon võtab eelneva väärtuse, newUser (vaikimisi väärtus on tühi objekt, {} kui redutseerimise viimased parameetrid) ja massiivi praegune element fn-na (pidage meeles, et vähendamine läbib massiivi). Lõpuks tagastab ta uue objekti, laiendades newUser (eelmine väärtus, mis iganes eelmine funktsioon tagasi andis). See annab meile sama tulemuse.

Selle kasutamise eeliseks on, et me ei pea enam andmeid ja newUserit tagastama ja neid argumendina kulutama, vaid saame andmed argumentidena ja newUser tagastame, see on palju lihtsam.

Mõlemad rakendused sobivad, see sõltub lihtsalt sellest, mida eelistate, nagu kõige rohkem, ning seda on teie ja teie meeskonna jaoks lihtsam lugeda ja hooldada.

Boonus

Argumendis ja tagastatud väärtuses pole vahet. Ka esinemine on paigas.

Javascriptis ei pööra me sellele lavastusele tähelepanu. Funktsionaalne programmeerimine on kallis. Selle paradigma aluseks olevad keeled muudavad koodi ja seda, kuidas nad käsitlevad massiivide, mälu jms käsitlemist kompileerimise ajal, kuid Javascript seda muidugi ei tee, see on lihtsalt skriptikeel. Me ei muretse selliste asjade pärast, kuna me ei tee rasket töötlemist, me teostame kliendi poolel kerget töötlemist ja keskendume serveripoolsele IO-le. Kui oleme jõudluse pärast mures, keskendume selle asemel, et niit ei blokeerita.

Öeldes, et funktsiooni koostis ja reduktor ei mõjuta Javascriptis toimuva valguse töötlemise toimivust, kuid erinevus on erinev.

Funktsiooni koosseis täidab kõnepakkumist kuni viimase funktsiooni naasmiseni. Selle põhjuseks on asjaolu, et kui me "kompileerime" funktsiooni javascriptis, teeme seda järgmiselt: getWebsite (getSocialMedia (getProfile (getID (data))))). Kõnepostil on 4 funktsiooni + komponeerimise funktsioon. Kui meil on keeruline protsess, kuna osa neist funktsioonidest on rekursiivne funktsioon, võiksime kõnede korstna täita liiga paljude elementidega ja meil on viga, näiteks "kõnede korstnate maksimaalne suurus ületatud". Selle asemel täidab Reducer kõnede korstna kolme funktsiooniga: getExposableUser, anonüümne fn edastatakse vähendamiseks ja praegune funktsioon massiivist. fn naaseb, see eemaldatakse kõnest ja järgmine fn paigutatakse kõne pinu, mis tähendab, et meil on kõnes kuni 3 funktsiooni. Kui protsess on massiline, võtab töötlemine kaua aega, blokeerime javascripti lõime, kuid me ei saa kunagi "maksimaalset kõnemahu suurust ületatud" (ainult juhul, kui ükski funktsioon pole muidugi rekursiivne).

Algselt avaldati veebiaadressil giulianok.com 18. augustil 2018.