Sklonost poznavanja vas zadržava: vrijeme je da prihvatite funkcije strelica

Ja podučavam JavaScript za život. Nedavno sam se vrtio oko svog nastavnog plana i programa kako bih ranije naučio funkcije sa zakrivljenim strelicama - u prvih nekoliko lekcija. Premjestio sam je ranije u nastavnom planu i programu jer je to izuzetno vrijedna vještina, a studenti su curry sa strelicama priješli puno brže nego što sam mislio da hoće.

Ako ga mogu razumjeti i iskoristiti to ranije, zašto ga ne podučiti ranije?

Napomena: Moji tečajevi nisu dizajnirani za ljude koji nikada nisu dotakli liniju koda. Većina studenata se pridružuje nakon što provode barem nekoliko mjeseci kodiranja - samostalno, u bootcam ili profesionalno. Međutim, vidio sam mnoge mlađe programere koji imaju malo ili nikakvog iskustva brzo poduzimaju ove teme.

Vidio sam gomilu učenika da se dobro upoznaju sa funkcijama zakrivljenih strelica unutar razdoblja jedne satnice. (Ako ste član "Learn JavaScript with Eric Elliott", sada možete gledati 55-minutnu lekciju ES6 Curry & Composition).

Uvidjevši kako ga učenici brzo podižu i počinju nositi svoje novonastale moći curryja, uvijek se pomalo iznenadim kada na Twitteru objavim funkcije zakrivljenih strelica, a Twitterverse s ogorčenjem reagira na pomisao da nanosi taj „nečitljiv“ kôd na ljudi koji će ga trebati održavati.

Prvo, dopustite mi da vam dam primjer onoga o čemu govorimo. Prvi put kad sam primijetio povratnu reakciju je Twitterov odgovor na ovu funkciju:

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

Bila sam šokirana kad su me ljudi na Twitteru optuživali da pokušavam zbuniti ljude. Napisao sam tu funkciju kako bih demonstrirao koliko je lako u ES6 lako izraziti ukrivljene funkcije. To je najjednostavnija praktična primjena i izraz zatvaranja koji se mogu sjetiti u JavaScriptu. (Povezano: "Što je zatvaranje?").

To je ekvivalentno sljedećem izrazu funkcije:

const secret = funkcija (msg) {
  povratna funkcija () {
    povrat msg;
  };
};

secret () je funkcija koja uzima msg i vraća novu funkciju koja vraća msg. Iskoristite prednosti zatvaranja kako biste fiksirali vrijednost msg na svaku vrijednost koju prebacite u tajnu ().

Evo kako ga upotrebljavate:

const mySecret = tajna ('bok');
moja tajna(); // 'bok'

Ispada da je "dvostruka strelica" ono što je zbunilo ljude. Uvjeren sam da je ovo činjenica:

Uz poznavanje, strelice u retku najčitaniji su način za izražavanje ukrivljenih funkcija u JavaScriptu.

Mnogi su mi tvrdili da je duži oblik lakše čitati nego kraći. Oni su djelomično u pravu, ali uglavnom u redu. Više je detaljan i eksplicitniji, ali nije lakši za čitanje, barem ne nekome tko je upoznat sa funkcijama strelica.

Prigovori koje sam vidio na Twitteru jednostavno se nisu poklapali s laganim iskustvom učenja mojih učenika. Po mom iskustvu, učenici se ponašaju sa strijelama poput riba koje vode u vodu. Za nekoliko dana od učenja, oni su jedno sa strelicama. Napori ih bez napora da se izbore sa svim vrstama problema kodiranja.

Ne vidim nikakav znak da su im strelice teško "naučiti", "čitati" ili "razumjeti" - nakon što su uložili početno ulaganje tijekom nekoliko satnih predavanja i predavanja.

Lako čitaju funkcije sa zakrivljenim strelicama koje nikada ranije nisu vidjeli i objašnjavaju mi ​​što se događa. Oni prirodno pišu svoje kad im predstavljam izazov.

Drugim riječima, čim se upoznaju s vidom funkcija strelice, oni s njima nemaju problema. Čitaju ih jednako lako kao što čitate ovu rečenicu - a njihovo se razumijevanje ogleda u mnogo jednostavnijem kodu s manje pogrešaka.

Zašto neki misle da naslijeđeni izrazi funkcija izgledaju "lakše"

Pristranost poznavanja je mjerljiva ljudska kognitivna pristranost koja nas dovodi do donošenja samodestruktivnih odluka, unatoč tome što smo svjesni bolje mogućnosti. Nastavljamo koristiti iste stare uzorke usprkos spoznaji o boljim uzorcima iz udobnosti i navika.

Možete naučiti puno više o pristranosti poznanstva (i o mnogim drugim načinima na koje se zavaravamo) iz izvrsne knjige „Projekt poništavanja: Prijateljstvo koje je promijenilo naše umove“. Ovu bi knjigu trebalo čitati svakom programeru softvera, jer vas potiče da razmišljate kritičnije i testirate svoje pretpostavke kako ne biste upadali u različite kognitivne zamke - a priča o tome kako su otkrivene te kognitivne zamke zaista je dobra, također ,

Naslijeđeni izrazi funkcija vjerojatno uzrokuju greške u vašem kodu

Danas sam prepisivao funkciju zakrivljene strelice s ES6 na ES5 kako bih je mogao objaviti kao modul s otvorenim kodom koji bi ljudi mogli koristiti u starim preglednicima bez prepisa. ES5 verzija me šokirala.

Verzija ES6 bila je jednostavna, kratka i elegantna - samo 4 linije.

To sam sigurno mislio, to je funkcija koja će Twitteru dokazati da su strelice superiorne i da bi ljudi trebali napustiti svoje naslijeđene funkcije poput loše navike u kojoj su.

Pa sam cvrkutao:

Evo teksta funkcija, u slučaju da slika ne radi za vas:

// zakrivljena strelicama
const composeMixins = (... mixins) => (
  instance = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => mix (... mixins) (instanca);
// prema ES5 stilu
var composeMixins = function () {
  var mixins = [] .slice.call (argumenti);
  funkcija povratka (primjerice, miks) {
    if (! instance) instanca = {};
    ako (! miks) {
      mix = funkcija () {
        var fns = [] .slice.call (argumenti);
        povratna funkcija (x) {
          return fns.reduce (funkcija (acc, fn) {
            uzvratiti fn (acc);
          }, x);
        };
      };
    }
    povratak mix.apply (null, mixins) (instance);
  };
};

Predmetna funkcija je jednostavan omotač oko cijevi (), standardni uslužni programski program koji se obično koristi za sastavljanje funkcija. Funkcija pipe () postoji u lodashu kao lodash / flow, u Ramdi kao R.pipe (), pa čak ima i svog operatora u nekoliko funkcionalnih programskih jezika.

To bi trebao biti poznat svima koji su upoznati s funkcionalnim programiranjem. Kao i njegova primarna ovisnost: Smanjiti.

U ovom se slučaju koristi za sastavljanje funkcionalnih miksa, ali to je irelevantan detalj (i čitav drugi blog blog). Evo važnih detalja:

Funkcija uzima bilo koji broj funkcionalnih mješavina i vraća funkciju koja ih primjenjuje jednu za drugom u cjevovodu - poput montažne linije. Svaki funkcionalni miks uzima instancu kao ulaz i na njega ubacuje neke stvari prije nego što je prebaci na sljedeću funkciju u cjevovodu.

Ako izostavite instancu, za vas će se stvoriti novi objekt.

Ponekad bismo možda željeli drugačije sastaviti miksere. Na primjer, možda ćete željeti proći compose () umjesto pipe () da biste promijenili redoslijed prioriteta.

Ako ne morate prilagođavati ponašanje, jednostavno ostavite zadani na miru i dobit ćete standardno ponašanje pipe ().

Samo činjenice

Mišljenja sa strane o čitljivosti, evo objektivnih činjenica koje se odnose na ovaj primjer:

  • Imam višegodišnje iskustvo s izrazima ES5 i ES6 funkcija, strelicama ili drugim. Pristranost poznavanja nije varijabla u ovim podacima.
  • Verziju ES6 napisao sam u nekoliko sekundi. Sadržao je nula grešaka (kojih sam svjestan - prolazi sve svoje jedinice jedinice).
  • Trebalo mi je nekoliko minuta da napišem ES5 verziju. Barem redom više vremena. Minut prema sekundama Dva puta sam izgubio mjesto u funkcijskim udjelima. Napisao sam 3 bugove, a sve sam morao ispraviti i ispraviti. Dvije od kojih sam morao pribjeći console.log () da shvatim što se događa.
  • Verzija ES6 je 4 retka koda.
  • Verzija ES5 dugačka je 21 redaka (17 zapravo sadrži kôd).
  • Unatoč svojoj zamornoj verbosnosti, verzija ES5 zapravo gubi dio vjernosti informacija koje su dostupne u ES6 verziji. Duže je, ali komunicira manje, čitajte dalje za detalje.
  • Verzija ES6 sadrži 2 namaza za parametre funkcija. Verzija ES5 izostavlja namaze i umjesto toga koristi objekt implicitnih argumenata, što šteti čitljivosti potpisa funkcije (vjernost prema dolje 1).
  • Verzija ES6 u potpisu funkcije definira zadanu vrijednost za miks, tako da možete jasno vidjeti da je vrijednost parametra. Verzija ES5 taj detalj zatamnjuje i umjesto toga skriva ga duboko u funkcijskom tijelu. (vjernost nadogradnji 2).
  • Verzija ES6 ima samo dvije razine uvlačenja, što pomaže razjasniti strukturu kako treba čitati. Verzija ES5 ima 6, a razine gniježđenja više nisu prikrivene, a ne pomažu čitljivosti strukture funkcije (vjernost prema dolje 3).

U verziji ES5, pipe () zauzima većinu tijela funkcije - toliko da je pomalo suludo definirati ga usmjernim. Doista je potrebno razbiti u zasebnu funkciju kako bi se verzija ES5 učinila čitljivom:

var cijev = funkcija () {
  var fns = [] .slice.call (argumenti);
  povratna funkcija (x) {
    return fns.reduce (funkcija (acc, fn) {
      uzvratiti fn (acc);
    }, x);
  };
};
var composeMixins = function () {
  var mixins = [] .slice.call (argumenti);
  funkcija povratka (primjerice, miks) {
    if (! instance) instanca = {};
    ako (! miks) mix = cijev;
    povratak mix.apply (null, mixins) (instance);
  };
};

To mi se čini očito čitljivijim i razumljivijim.

Pogledajmo što se događa kada na ES6 verziju primenimo istu „optimizaciju“ čitljivosti:

const cijev = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x);
const composeMixins = (... mixins) => (
  instance = {},
  miješati = cijev
) => mix (... mixins) (instanca);

Kao i ES5 optimizacija, ova je verzija više složena (dodaje novu varijablu koja ranije nije bila). Za razliku od verzije ES5, ova verzija nije značajno čitljivija nakon apstrakcije definicije cijevi. Napokon je već imao naziv varijable koji mu je jasno dodijeljen u potpisu funkcije: mix.

Definicija miksa već je sadržana u vlastitoj liniji, zbog čega se čitatelji malo mogu zbuniti oko toga gdje završava, a ostatak funkcije se nastavlja.

Sada imamo dvije varijable koje predstavljaju istu stvar umjesto 1. Jesmo li stekli jako puno? Nije očito, ne.

Pa zašto je ES5 verzija očito bolja sa istom funkcijom apstrahirane?

Jer je verzija ES5 očito složenija. Izvor te složenosti je srž ove materije. Tvrdim da se izvor složenosti svodi na sintaksalni šum, a da sintaksa šuma zastira značenje funkcije, ne pomažući.

Promijenimo zupčanike i eliminiramo još nekih varijabli. Upotrijebimo ES6 za oba primjera i usporedimo samo funkcije strelice sa naslijeđenim izrazima funkcija:

var composeMixins = funkcija (... mixins) {
  povratna funkcija (
    instance = {},
    mix = funkcija (... fns) {
      povratna funkcija (x) {
        return fns.reduce (funkcija (acc, fn) {
          uzvratiti fn (acc);
        }, x);
      };
    }
  ) {
    povratni miks (... mixins) (instanca);
  };
};

To mi se čini značajno čitljivijim. Sve što smo promijenili jest da iskoristimo sintaksu odmora i zadanih parametara. Naravno, morat ćete biti upoznati sa sintaksom odmora i zadanom postavom kako bi ova verzija bila čitljivija, ali čak i ako niste, mislim da je očito da je ova verzija još uvijek manje skučena.

To je puno pomoglo, ali još uvijek mi je jasno da je ova verzija još uvijek toliko skučena da bi apstrahiranje cijevi () u svoju funkciju očito moglo pomoći:

const cijev = funkcija (... fns) {
  povratna funkcija (x) {
    return fns.reduce (funkcija (acc, fn) {
      uzvratiti fn (acc);
    }, x);
  };
};
// Naslijeđeni izrazi funkcije
const composeMixins = funkcija (... mixins) {
  povratna funkcija (
    instance = {},
    miješati = cijev
  ) {
    povratni miks (... mixins) (instanca);
  };
};

To je bolje, zar ne? Sada, kada dodjeljivanje miksa zauzima samo jednu liniju, struktura funkcije je mnogo jasnija - ali i dalje je previše sintaksa po mom ukusu. U composeMixins (), na prvi pogled mi nije jasno gdje se završava jedna funkcija, a počinje druga.

Umjesto da pozivaju funkcionalna tijela, čini se da se ova funkcionalna ključna riječ vizualno stapa s identifikatorima oko nje. Postoje funkcije koje se kriju u mojoj funkciji! Gdje završava potpis parametra i tijelo funkcije? Mogu shvatiti ako gledam izbliza, ali meni to nije vizualno očito.

Što ako se možemo riješiti funkcionalne ključne riječi i pozvati povratne vrijednosti vizualno ih pokazujući velikom strelicom masti => umjesto da napišemo povratnu ključnu riječ koja se stapa s okolnim identifikatorima?

Ispada, možemo, a evo kako to izgleda:

const composeMixins = (... mixins) => (
  instance = {},
  miješati = cijev
) => mix (... mixins) (instanca);

Sada bi trebalo biti jasno što se događa. composeMixins () je funkcija koja uzima bilo koji broj mixina i vraća funkciju koja uzima dva neobavezna parametra, instance i miks. Vraća rezultat primjerice cjevovoda kroz sastavljene smjese.

Još samo jedna stvar ... ako primijenimo istu optimizaciju na pipe (), ona se magično pretvara u jednoslojni:

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

S tom je definicijom u jednom redu manje jasna prednost apstrahiranja iste u vlastitoj funkciji. Zapamtite, ova funkcija postoji kao uslužni program u Lodashu, Ramdi i hrpi drugih knjižnica, ali je li zaista vrijedno zalogajiti za uvoz druge biblioteke?

Je li uopće vrijedno povući je u svoju liniju? Vjerojatno. Oni su uistinu dvije različite funkcije, a njihovo razdvajanje to postaje jasnije.

S druge strane, njegovo povezivanje unaprijed pojašnjava očekivanja o vrsti i upotrebi kada pogledate potpis parametra. Evo što se događa kada ga stvorimo linijski:

const composeMixins = (... mixins) => (
  instance = {},
  mix = (... fns) => x => fns.reduce ((acc, fn) => fn (acc), x)
) => mix (... mixins) (instanca);

Sada smo se vratili izvornoj funkciji. Uz put, nismo odbacili nikakvo značenje. Zapravo, proglašavanjem naših parametara i zadanih vrijednosti ugrađenim, dodali smo informacije o tome kako se funkcija koristi i kako bi vrijednosti parametara mogle izgledati.

Sav taj dodatni kod u ES5 verziji bio je samo šum. Sintaksa. Nije se koristio nikakvoj korisnoj svrsi, osim da potakne ljude koji nisu upoznati sa zakrivljenim funkcijama strelica.

Jednom kada steknete dovoljno poznavanja funkcija sa strelicama, trebalo bi biti jasno da je izvorna verzija čitljivija, jer postoji puno manje sintakse u koju se možete izgubiti.

Također je i manje sklon greškama, jer ima puno manje površine za sakrivanje grešaka.

Pretpostavljam da se u nasljeđenim funkcijama skriva puno grešaka koji bi se pronašli i uklonili ako nadogradite na funkcije strelice.

Također vjerujem da bi vaš tim postao značajno produktivniji ako biste naučili prihvaćati i favorizirati više sažetu sintaksu dostupnu u ES6.

Iako je istina da je ponekad stvari lakše razumjeti ako su izričite, ali također je istina da je manje koda bolje.

Ako manje koda može postići istu stvar i komunicirati više, a da pritom ne žrtvuje nikakvo značenje, to je objektivno bolje.

Ključ spoznaje razlike je smisao. Ako više koda ne doda više značenja, taj kôd ne bi trebao postojati. Taj je koncept tako osnovni, to je dobro poznata stilska smjernica prirodnog jezika.

Ista smjernica stila odnosi se na izvorni kod. Prigrlite ga i vaš će kod biti bolji.

Na kraju dana, svjetlost u tami. Kao odgovor na još jedan tvit koji kaže da je verzija ES6 manje čitljiva:

Vrijeme je za upoznavanje sa ES6, curryingom i sastavom funkcije.

Sljedeći koraci

Članovi „Saznajte JavaScript s Ericom Elliottom“ trenutno mogu gledati 55-minutnu lekciju Curry & Composition ES6.

Ako niste član, nedostaje vam!

Eric Elliott autor je programa „Programiranje JavaScripta“ (O’Reilly) i „Saznajte JavaScript s Ericom Elliottom“. Doprinosio je softverskim iskustvima za Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC i vrhunske umjetnike za snimanje, uključujući Usher, Frank Ocean, Metallicu i mnoge druge.

Većinu svog vremena provodi u predjelu zaljeva San Francisco s najljepšom ženom na svijetu.