Tunnettavuusobjekti pidättelee sinua: on aika omaksua nuolitoiminnot

”Ankkuri” - Näyttelijä212 - (CC BY-NC-ND 2.0)

Opetan JavaScriptiä elämiseen. Viime aikoina olen sekoittunut opetussuunnitelmani ympärille opettaaksesi curryt nuolitoimintoja nopeammin - muutaman ensimmäisen tunnin aikana. Siirtin sen aikaisemmin opetussuunnitelmaan, koska se on erittäin arvokas taito, ja opiskelijat poimivat nuolilla harjoittelua paljon nopeammin kuin luulin tekevän.

Jos he ymmärtävät sen ja hyödyntävät sitä aikaisemmin, miksi et opettaisi sitä aikaisemmin?

Huomaa: Kursseja ei ole suunniteltu ihmisille, jotka eivät ole koskaan koskettaneet koodiriviä aikaisemmin. Suurin osa opiskelijoista liittyy siihen, kun on viettänyt vähintään muutaman kuukauden koodausta - yksin, käynnistysleirillä tai ammattimaisesti. Olen kuitenkin nähnyt, että monet nuoremmat kehittäjät, joilla on vähän kokemusta tai joilla ei ole kokemusta, hakevat nämä aiheet nopeasti.

Olen nähnyt joukon opiskelijoita saamaan tuntemuksen currytuista nuolitoiminnoista yhden tunnin oppitunnin kuluessa. (Jos olet “Opi JavaScript Eric Elliottin kanssa” jäsen, voit katsoa 55 minuutin ES6 Curry & Composition -oppitunnin heti).

Nähdessään kuinka nopeasti opiskelijat noutavat sen ja alkavat käyttää uusia löydettyjä curryvaltuuksiaan, olen aina vähän yllättynyt, kun postitan curryt nuolitoiminnot Twitterissä, ja Twitterverse vastaa järkyttyneenä ajatukseen aiheuttaa tämä "lukematon" koodi ihmisiä, joiden on ylläpidettävä sitä.

Ensinnäkin annan teille esimerkin siitä, mistä me puhumme. Ensimmäistä kertaa huomasin vastavälin oli Twitter-vastaus tähän toimintoon:

const secret = msg => () => msg;

Olin järkyttynyt, kun Twitterissä ihmiset syyttivät minua yrittämään sekoittaa ihmisiä. Kirjoitin tämän funktion osoittaakseen, kuinka helppoa on ilmaista curred-funktioita ES6: ssa. Se on yksinkertaisin käytännöllinen sovellus ja sulkemisen ilmaus, jota voin ajatella JavaScriptin avulla. (Liittyy: “Mikä on sulkeminen?”).

Se vastaa seuraavaa funktiolauseketta:

const secret = funktio (msg) {
  paluutoiminto () {
    palauta viesti;
  };
};

secret () on funktio, joka vie viestin ja palauttaa uuden funktion, joka palauttaa viestin. Sulkimet hyödynnetään vahvistaaksesi msg: n arvon mihin tahansa arvoon, jonka siirrät salaiseksi ().

Näin käytät sitä:

const mySecret = salainen ('hei');
minun salaisuuteni(); // 'Hei'

Osoittautuu, että kaksoisnuoli on sekava, mikä hämmentää ihmisiä. Olen vakuuttunut siitä, että tämä on totta:

Tunnetulla tavalla rivillä olevat nuolitoiminnot ovat luettavin tapa ilmaista uteltuja toimintoja JavaScriptillä.

Monet ihmiset ovat väittäneet minulle, että pidempi muoto on helpompi lukea kuin lyhyempi. He osittain oikeassa, mutta enimmäkseen väärä. Se on sanallisempi ja selkeämpi, mutta ei helpompi lukea - ainakaan jollekin nuolitoimintojen tuntevalle.

Twitterissä näkemäni vastaväitteet eivät vain edenneet opiskelijoiden nauttivan sujuvan oppimiskokemuksen kanssa. Kokemukseni mukaan opiskelijat käyttävät hierottuja nuolitoimintoja, kuten kalat veteen. Muutaman päivän kuluessa heidän oppimisestaan ​​he ovat yksi nuolella. He heittävät heitä vaivattomasti vastaamaan kaikenlaisiin koodaushaasteisiin.

En näe merkkejä siitä, että nuolitoimintojen oppiminen, lukeminen tai ymmärtäminen on ”vaikeaa” heille, kun he ovat tehneet alkuinvestoinnin oppia niitä muutaman tunnin kestävien oppituntien ja opintojaksojen aikana.

He lukevat helposti currytut nuolitoiminnot, joita eivät ole koskaan nähneet, ja selittävät minulle mitä tapahtuu. He luonnollisesti kirjoittavat omat, kun esitän heille haaste.

Toisin sanoen heti kun he ovat tutustuneet näkemään curryt nuolitoiminnot, heillä ei ole mitään ongelmia heidän kanssaan. He lukevat ne yhtä helposti kuin luet tämän lauseen - ja heidän ymmärryksensä heijastuu paljon yksinkertaisemmassa koodissa, jossa on vähemmän virheitä.

Miksi jotkut ihmiset ajattelevat vanhojen funktiolausekkeiden näyttävän "helpommalta" luettavalta

Tuntevuuspoikkeama on mitattavissa oleva ihmisen kognitiivinen ennakkoluulo, joka johtaa meitä tekemään itsetuhoisia päätöksiä huolimatta siitä, että olemme tietoisia paremmasta vaihtoehdosta. Käytämme samoja vanhoja malleja huolimatta siitä, että tiedämme paremmista malleista mukavuuden ja tavan ulkopuolella.

Voit oppia paljon enemmän perehtyneisyyteen liittyvistä puolueellisuuksista (ja monista muista tapoista huijata itseämme) erinomaisesta kirjasta ”Undoing Project: Ystävyys, joka muutti mielemme”. Tämän kirjan pitäisi olla luettava jokaiselle ohjelmistokehittäjälle, koska se rohkaisee sinua ajattelemaan kriittisemmin ja testaamaan oletuksiasi, jotta vältetään joutumasta moniin kognitiivisiin loukkuihin - ja tarina siitä, kuinka nuo kognitiiviset loukut löydettiin, on myös todella hyvä .

Vanhat funktiolausekkeet ovat todennäköisesti aiheuttaneet virheitä koodissasi

Kirjoitin tänään curry-nuolitoiminnon ES6: sta ES5: ksi, jotta voisin julkaista sen avoimen lähdekoodin moduulina, jota ihmiset voisivat käyttää vanhoissa selaimissa transpiloimatta. ES5-versio järkytti minua.

ES6-versio oli yksinkertainen, lyhyt ja tyylikäs - vain 4 riviä.

Ajattelin varmasti, että tämä toiminto osoitti Twitterille, että nuolitoiminnot ovat parempia ja että ihmisten tulisi luopua vanhoista toiminnoistaan, kuten huonosta tavastaan.

Joten twiitin:

Tässä on funktioiden teksti, jos kuva ei toimi sinulle:

// hierottu nuolilla
const composeMixins = (... mixins) => (
  esiintymä = {},
  sekoita = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => sekoita (... sekoittaa) (esimerkki);
// vs. ES5-tyyli
var composeMixins = function () {
  var mixins = [] .lice.call (argumentit);
  paluutoiminto (esiintymä, sekoitus) {
    if (! esiintymä) esiintymä = {};
    if (! sekoita) {
      sekoita = funktio () {
        var fns = [] .lice.call (argumentit);
        paluutoiminto (x) {
          return fns.reduce (toiminto (acc, fn) {
            paluu fn (acc);
          }, x);
        };
      };
    }
    palauta mix.apply (nolla, mixins) (esiintymä);
  };
};

Kyseinen toiminto on yksinkertainen kääre putken () ympärillä, tavallinen toiminnallinen ohjelmointiohjelma, jota käytetään yleisesti toimintojen muodostamiseen. Putki () -funktio esiintyy lodashissa kuten lodash / flow, Ramdassa kuin R.pipe (), ja sillä on jopa oma operaattorinsa useilla toiminnallisilla ohjelmointikielellä.

Sen tulisi olla tuttu kaikille, jotka tuntevat toiminnallisen ohjelmoinnin. Kuten sen ensisijainen riippuvuus: Vähennä.

Tässä tapauksessa sitä käytetään säveltämään toiminnallisia miksauksia, mutta se ei ole merkityksellinen yksityiskohta (ja kokonaan toinen blogin viesti). Tässä ovat tärkeät yksityiskohdat:

Toiminto ottaa määrän funktionaalisia sekoituksia ja palauttaa toiminnon, joka soveltaa niitä peräkkäin putkilinjassa - kuten kokoonpanolinja. Jokainen toiminnallinen sekoitus ottaa esiintymän syötteenä ja tarttuu siihen joitain asioita ennen siirtämistä seuraavaan toimintoon putkilinjassa.

Jos ohitat esiintymän, uusi objekti luodaan sinulle.

Joskus saatamme haluta säveltää miksauksia eri tavalla. Voit esimerkiksi siirtää säveltä () piipun () sijasta kääntääksesi tärkeysjärjestyksen.

Jos sinun ei tarvitse mukauttaa käyttäytymistä, jätä oletus vain yksin ja saat vakiona pipe () -käyttäytymisen.

Vain tosiasiat

Mielipiteitä lukukelpoisuudesta lukuun ottamatta, tässä ovat objektiiviset tosiseikat, jotka liittyvät tähän esimerkkiin:

  • Minulla on monen vuoden kokemus sekä ES5: n että ES6: n funktiolausekkeista, nuoleista tai muista. Tuntevuuspoikkeama ei ole muuttuja näissä tiedoissa.
  • Kirjoitin ES6-version muutamassa sekunnissa. Se sisälsi nolla virhettä (joista olen tietoinen - se läpäisee kaikki yksikkötestinsä).
  • ES5-version kirjoittaminen kesti useita minuutteja. Ainakin suuruusluokkaa enemmän aikaa. Minuuttia vs. sekuntia. Menetin paikkani funktion sisennyksissä kahdesti. Kirjoitin 3 virhettä, jotka minun piti korjata ja korjata. Kaksi niistä, jotka minun piti turvautua console.log () -järjestelmään selvittääkseni mitä tapahtui.
  • ES6-versio on 4 koodiriviä.
  • ES5-versio on 21 riviä pitkä (17 sisältää todella koodin).
  • Ärsyttävästä moninaisuudesta huolimatta ES5-versio todellakin menettää osan ES6-version käytettävissä olevasta tietojen uskollisuudesta. Se on paljon pidempi, mutta kommunikoi vähemmän, lue lisätietoja.
  • ES6-versio sisältää 2 funktioparametrien hajotusta. ES5-versio jättää hajotukset pois ja käyttää sen sijaan implisiittisiä argumentteja -objektia, mikä vahingoittaa funktion allekirjoituksen luettavuutta (uskollisuusluokka 1).
  • ES6-versio määrittelee sekoituksen oletusasetuksen funktion allekirjoituksessa, jotta voit selvästi nähdä, että se on parametrin arvo. ES5-versio hämärtää tämän yksityiskohdan ja piilottaa sen sijaan syvälle toimintorunkoon. (uskollisuuden alennus 2).
  • ES6-versiossa on vain kaksi sisennystasoa, mikä auttaa selventämään sen lukemisen rakennetta. ES5-versiossa on 6, ja pesimistasot peittävät pikemminkin kuin helpottavat funktion rakenteen luettavuutta (uskollisuusluokka 3).

ES5-versiossa putki () vie suurimman osan toimintokappaleesta - niin paljon, että on vähän hullu määritellä se sisäisesti. Se on todellakin hajoteltava erilliseen toimintoon, jotta ES5-versio olisi luettavissa:

var pipe = function () {
  var fns = [] .lice.call (argumentit);
  paluutoiminto (x) {
    return fns.reduce (toiminto (acc, fn) {
      paluu fn (acc);
    }, x);
  };
};
var composeMixins = function () {
  var mixins = [] .lice.call (argumentit);
  paluutoiminto (esiintymä, sekoitus) {
    if (! esiintymä) esiintymä = {};
    if (! sekoita) sekoita = putki;
    palauta mix.apply (nolla, mixins) (esiintymä);
  };
};

Tämä vaikuttaa selvästi luettavalta ja ymmärrettävältä minulle.

Katsotaanpa mitä tapahtuu, kun sovellamme samaa luettavuuden optimointia ES6-versioon:

const pipe = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);
const composeMixins = (... mixins) => (
  esiintymä = {},
  sekoita = putki
) => sekoita (... sekoittaa) (esimerkki);

Kuten ES5-optimointi, tämä versio on kattavampi (se lisää uuden muuttujan, jota ei ollut siellä aiemmin). Toisin kuin ES5-versio, tämä versio ei ole merkittävästi luettavissa putken määritelmän tiivistämisen jälkeen. Loppujen lopuksi sille oli jo määritetty muuttujan nimi selvästi funktion allekirjoituksessa: sekoita.

Sekoituksen määritelmä sisältyi jo omaan riviinsa, minkä vuoksi lukijoiden on epätodennäköistä, että ne sekoittuvat sen loppumiseen ja muun toiminnan jatkaminen.

Nyt meillä on 2 muuttujaa, jotka edustavat samaa asiaa yhden sijaan. Olemmeko saaneet paljon? Ei selvästi, ei.

Joten miksi ES5-versio on selvästi parempi samalla toiminnolla?

Koska ES5-versio on selvästi monimutkaisempi. Tämän monimutkaisuuden lähde on tämän asian ydin. Väitän, että monimutkaisuuden lähde kiehuu syntaksimeluun ja että syntaksimelu hämärtää funktion tarkoituksen, ei auta.

Vaihdetaan vaihteet ja poistetaan vielä muuttujat. Käytämme ES6: ta molemmissa esimerkeissä ja vertaa vain nuolen toimintoja vs. vanhoja funktiolausekkeita:

var composeMixins = funktio (... mixins) {
  paluutoiminto (
    esiintymä = {},
    sekoita = funktio (... fns) {
      paluutoiminto (x) {
        return fns.reduce (toiminto (acc, fn) {
          paluu fn (acc);
        }, x);
      };
    }
  ) {
    paluusekoitus (... sekoitukset) (esimerkki);
  };
};

Tämä näyttää minusta huomattavasti paremmin luettavalta. Muutimme vain sitä, että hyödynnämme lepoajan ja oletusparametrien syntaksia. Tietysti joudut tuntemaan lepo- ja oletussyntaksin, jotta tämä versio olisi paremmin luettavissa, mutta vaikka et ole, mielestäni on selvää, että tämä versio on silti vähemmän sotkuinen.

Se auttoi paljon, mutta minulle on silti selvää, että tämä versio on silti riittävän sotkuinen, että putken () hankkiminen omaan funktioonsa auttaisi tietysti:

const pipe = toiminto (... fns) {
  paluutoiminto (x) {
    return fns.reduce (toiminto (acc, fn) {
      paluu fn (acc);
    }, x);
  };
};
// Vanhat funktiolausekkeet
const composeMixins = funktio (... mixins) {
  paluutoiminto (
    esiintymä = {},
    sekoita = putki
  ) {
    paluusekoitus (... sekoitukset) (esimerkki);
  };
};

Se on parempi, eikö niin? Nyt kun sekoitusmääritys vie vain yhden rivin, funktion rakenne on paljon selkeämpi - mutta syntaksimelua on edelleen liian paljon maulleni. ComposMixins () -sovelluksessa ei ole minulle yhdellä silmäyksellä selvää, missä yksi toiminto loppuu ja toinen alkaa.

Sen sijaan, että kutsutaan toimintoelimiä, toimintoavainsana näyttää visuaalisesti sekoittuvan sen ympärillä oleviin tunnisteisiin. Toiminnossani on piilossa toimintoja! Missä parametrien allekirjoitus loppuu ja funktion runko alkaa? Voin selvittää sen, jos tarkastelen tarkkaan, mutta se ei ole minulle visuaalisesti ilmeinen.

Entä jos voisimme päästä eroon funktion avainsanasta ja kutsua palautusarvot esiin osoittamalla niitä visuaalisesti suurella rasvanuolella => sen sijaan, että kirjoittaisimme palautusavaimen, joka sekoittuu ympäröiviin tunnisteisiin?

Selviää, voimme, ja tässä näyttää siltä, ​​miltä se näyttää:

const composeMixins = (... mixins) => (
  esiintymä = {},
  sekoita = putki
) => sekoita (... sekoittaa) (esimerkki);

Nyt pitäisi olla selvää mitä tapahtuu. composeMixins () on funktio, joka vie minkä tahansa määrän yhdistelmiä ja palauttaa toiminnon, joka ottaa kaksi valinnaista parametria, esiintymä ja sekoitus. Se palauttaa putkiston esiintymän muodostettujen sekoitusten kautta.

Vielä yksi asia ... jos sovellamme samaa optimointia putkeen (), se muuttuu taianomaisesti yhdeksi vuoraukseksi:

const pipe = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);

Kun määritelmä on yksirivinen, etuna on sen abstraktiointi omaan funktioonsa vähemmän selvä. Muista, että tämä toiminto on apuohjelma Lodashissa, Ramdassa ja joukossa muita kirjastoja, mutta onko toisen kirjaston tuonti todella syytä?

Kannattaako edes vetää se ulos omaan linjaansa? Todennäköisesti. Ne ovat todella kaksi erilaista toimintoa, ja niiden erottaminen tekee siitä selvemmän.

Toisaalta, jos se on linjassa, se selventää tyypin ja käytön odotuksia, kun tarkastelet parametrien allekirjoitusta. Näin tapahtuu, kun luomme sen riviin:

const composeMixins = (... mixins) => (
  esiintymä = {},
  sekoita = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => sekoita (... sekoittaa) (esimerkki);

Nyt olemme palanneet alkuperäiseen toimintoon. Koko ajan emme hylänneet mitään merkitystä. Itse asiassa, julistamalla parametrit ja oletusarvot riviin, lisäsimme tietoa toiminnon käytöstä ja mistä parametrien arvot saattavat näyttää.

Koko tuo ylimääräinen koodi ES5-versiossa oli vain melua. Syntaksimelu. Sillä ei ollut mitään hyödyllistä tarkoitusta, lukuun ottamatta ihmisten sopeutumista, jotka eivät tunne curryt nuolitoimintoja.

Kun olet riittävästi perehtynyt curry-nuolitoimintoihin, pitäisi olla selvää, että alkuperäinen versio on luettavissa, koska eksyä on paljon vähemmän syntaksia.

Se on myös vähemmän virhealttiita, koska virheiden piilossa on paljon vähemmän pintaa.

Epäilen, että vanhoissa toiminnoissa on piilossa paljon virheitä, jotka löydetään ja poistetaan, jos päivität nuolitoimintoihin.

Uskon myös, että ryhmästäsi tulee huomattavasti tuottavampaa, jos opit omaksumaan ja suosimaan enemmän ES6: n ytimekäs syntaksia.

Vaikka on totta, että joskus asiat ovat helpompi ymmärtää, jos ne tehdään selkeinä, on totta, että yleensä vähemmän koodi on parempi.

Jos vähemmän koodi voi suorittaa saman asian ja kommunikoida enemmän, uhraamatta mitään merkitystä, se on objektiivisesti parempi.

Avain tietäen ero on merkitys. Jos useampi koodi ei lisää merkitystä, sitä ei pitäisi olla. Tämä käsite on niin yksinkertainen, se on luonnollisen kielen tunnettu tyyliohje.

Sama tyyliohje koskee lähdekoodia. Ota se vastaan, ja koodisi on parempi.

Päivän lopussa valo pimeässä. Vastauksena vielä toiseen twiittiin, jossa sanotaan, että ES6-versio on vähemmän luettavissa:

Aika tutustua ES6: een, currymiseen ja funktion koostumukseen.

Seuraavat vaiheet

”Opi JavaScript Eric Elliottin kanssa” jäsenet voivat seurata 55 minuutin ES6 Curry & Composition -tunnia heti.

Jos et ole jäsen, menetät ikäväsi!

Eric Elliott on kirjoittanut “Ohjelmointi JavaScript-sovellukset” (O’Reilly) ja “Opi JavaScript Eric Elliottin kanssa”. Hän on osallistunut ohjelmistokokemuksiin Adobe Systemsille, Zumba Fitnessille, The Wall Street Journalille, ESPN: lle, BBC: lle ja parhaille äänittäjille, mukaan lukien Usher, Frank Ocean, Metallica ja monille muille.

Hän viettää suurimman osan ajastaan ​​San Franciscon lahden alueella maailman kauneimman naisen kanssa.