Породична пристраност вас задржава: време је за прихватање функција стрелица

„Сидро“ - Ацтор212 - (ЦЦ БИ-НЦ-НД 2.0)

Ја учим ЈаваСцрипт за живот. Недавно сам се промешкољивао око свог наставног плана и програма како бих раније предавао функције уклете стрелице - у првих неколико лекција. Премјестио сам га раније у наставном плану и програму јер је то изузетно драгоцена вештина, а ученици савијају цурице стрелицама много брже него што сам мислио да хоће.

Ако то могу да разумеју и искористе раније, зашто га не подучити раније?

Напомена: Моји курсеви нису дизајнирани за људе који никада раније нису додирнули линију кода. Већина студената се придружује након што проводе најмање неколико месеци у кодирању - самостално, у боотцам или професионално. Међутим, видео сам многе млађе програмере који имају мало или никаквог искуства брзо извлаче ове теме.

Видео сам гомилу ученика да се добро упознају са функцијама закривљених стрелица унутар домета једне сатнице. (Ако сте члан „Леарн ЈаваСцрипт витх Ериц Еллиотт“, сада можете гледати 55-минутну лекцију ЕС6 Цурри & Цомпоситион).

Гледајући како ученици брзо то прихватају и почињу да носе своје новонастале моћи цуррија, увек се помало изненадим када на Твиттеру објавим функције укривљених стрелица, а Твиттерверсе с огорчењем одговара на помисао да наноси тај „нечитљив“ код на људи који ће га требати одржавати.

Прво, дозволите ми да вам дам пример о чему причамо. Први пут када сам приметио повратну реакцију био је Твиттер одговор на ову функцију:

цонст сецрет = мсг => () => мсг;

Била сам шокирана кад су ме људи на Твиттеру оптужили да покушавам збунити људе. Написао сам ту функцију како бих демонстрирао колико је лако у ЕС6 лако изразити уклете функције. То је најједноставнија практична примена и израз затварања којег се могу сјетити у ЈаваСцрипт-у. (Повезано: „Шта је затварање?“).

То је еквивалентно следећем изразу функције:

цонст сецрет = функција (мсг) {
  повратна функција () {
    ретурн мсг;
  };
};

сецрет () је функција која узима мсг и враћа нову функцију која враћа мсг. Искористите предности затварања како бисте фиксирали вредност мсг на било коју вредност коју пребаците у тајну ().

Ево како га користите:

цонст миСецрет = тајна ('бок');
моја тајна(); // 'Ћао'

Испада да је "двострука стрелица" оно што је збунило људе. Уверен сам да је ово чињеница:

Уз познавање функција, стрелице у линији су најчитанији начин за изражавање укривљених функција у ЈаваСцрипт-у.

Многи људи су ми тврдили да је дужи облик лакше читати него краћи. Они су делимично у праву, али углавном су у криву. То је много сложеније и експлицитније, али није лакше за читање - барем не неком ко је упознат са функцијама стрелица.

Приговори које сам видио на Твиттеру нису само стопили са углађеним искуством учења мојих ученика. По мом искуству, студенти се залажу за функције стрелице попут риба које воде у воду. За неколико дана од учења, они су једно са стрелицама. Они их без напора решавају у разне изазове кодирања.

Не видим никакав знак да су стрелице „тешке“ за њих да науче, читају или не разумеју - након што су уложили почетно улагање у учењу током неколико сатних предавања и предавања.

Лако читају закривљене функције стрелице које никада раније нису видели и објашњавају ми шта се догађа. Они природно пишу своје кад им представљам изазов.

Другим речима, чим се упознају са виђењем функција са стрелицом, они немају проблема са њима. Читају их једнако лако као што читате ову реченицу - а њихово разумевање огледа се у много једноставнијем коду са мање погрешака.

Зашто неки мисле да наслијеђени изрази функција изгледају „лакше“ за читање

Предрасуда познанства су мерљива људска когнитивна пристраност која нас доводи до доношења самодеструктивних одлука упркос томе што смо свесни боље могућности. Настављамо са кориштењем истих старих образаца упркос томе што знамо о бољим узорцима из удобности и навика.

Можете научити пуно више о пристраности познанства (и о многим другим начинима на које се заваравамо) из одличне књиге „Пројект поништавања: Пријатељство које је променило наше мисли“. Ову би књигу требало читати сваком програмеру софтвера, јер вас подстиче да размишљате критичније и тестирате своје претпоставке да не бисте запали у разне когнитивне замке - а прича о томе како су откривене те когнитивне замке заиста је добра, такође .

Наслијеђени изрази функција вјероватно узрокују грешке у вашем коду

Данас сам преписивао функцију закривљене стрелице са ЕС6 на ЕС5 да бих је могао објавити као модул отвореног кода који би људи могли да користе у старим прегледачима без преношења. ЕС5 верзија ме шокирала.

ЕС6 верзија је била једноставна, кратка и елегантна - само 4 линије.

Сигурно сам мислио да је ово функција која ће Твиттеру доказати да су стрелице супериорне и да би људи требало да напусте наслеђене функције попут лоше навике какве имају.

Тако сам твитовао:

Ево текста функција, у случају да слика не ради за вас:

// закривљена стрелицама
цонст цомпосеМикинс = (... микинс) => (
  инстанце = {},
  мик = (... фнс) => к => фнс.редуце ((ацц, фн) => фн (ацц), к)
) => мик (... микинс) (примерак);
// вс ЕС5-стиле
вар цомпосеМикинс = фунцтион () {
  вар микинс = [] .слице.цалл (аргументи);
  функција повратка (примера, микс) {
    иф (! инстанце) инстанца = {};
    иф (! мик) {
      мик = функција () {
        вар фнс = [] .слице.цалл (аргументи);
        повратна функција (к) {
          ретурн фнс.редуце (функција (ацц, фн) {
            ретурн фн (ацц);
          }, Икс);
        };
      };
    }
    повратак мик.аппли (нулл, микинс) (примерак);
  };
};

Дотична функција је једноставан омотач око цеви (), стандардни услужни програмски програм који се обично користи за састављање функција. Функција пипе () постоји у лодасх-у као лодасх / флов, у Рамди као Р.пипе (), па чак има и свог оператора у неколико функционалних програмских језика.

Требало би да буде познато свима који познају функционално програмирање. Као и његова примарна зависност: Смањити.

У овом случају се користи за састављање функционалних микса, али то је небитан детаљ (и читав други пост на блогу). Ево важних детаља:

Функција узима било који број функционалних комбинација и враћа функцију која их примењује једну за другом у цевоводу - попут монтажне линије. Сваки функционални миксин примерак узима као улаз и на њега убацује неке ствари пре него што га пребаци на следећу функцију у цевоводу.

Ако изоставите инстанцу, ствара се нови објекат за вас.

Понекад можда желимо да саставимо миксере другачије. На пример, можда ћете желети да пошаљете цомпосе () уместо пипе () да бисте преокренули редослед приоритета.

Ако не морате да прилагођавате понашање, једноставно оставите задану задатку на миру и добићете стандардно понашање ().

Само чињенице

Мишљења о читљивости на страну, ево објективних чињеница које се односе на овај пример:

  • Имам вишегодишње искуство са изразима ЕС5 и ЕС6 функција, стрелицама или слично. Пристраност познавања није променљива у овим подацима.
  • Написао сам ЕС6 верзију за неколико секунди. Садржао је нула грешака (којих сам свестан - пролази све своје јединице јединице).
  • Требало ми је неколико минута да напишем ЕС5 верзију. Барем редом више времена. Минут према секундама Два пута сам изгубио место у функцијама. Написао сам 3 грешке, све које сам морао да исправим и исправим. Два од којих сам морала да прибегнем цонсоле.лог () да бих схватила шта се догађа.
  • Верзија ЕС6 је 4 кода.
  • Верзија ЕС5 дугачка је 21 линија (17 заправо садржи код).
  • Упркос својој заморној вербосности, верзија ЕС5 заправо губи део верности информација које су доступне у ЕС6 верзији. Много је дуже, али комуницира мање, читајте даље за детаље.
  • Верзија ЕС6 садржи 2 намаза за параметре функција. Верзија ЕС5 изоставља намазе и уместо тога користи објект имплицитних аргумената, што штети читљивости потписа функције (верност према доље 1).
  • Верзија ЕС6 у потпису функције дефинира задану вриједност за микс, тако да можете јасно видјети да је вриједност параметра. Верзија ЕС5 тај детаљ затамњује и уместо тога га скрива дубоко у функционом тијелу. (верност верзије 2).
  • Верзија ЕС6 има само 2 нивоа увлачења, што помаже да се разјасни структура начина на који треба читати. Верзија ЕС5 има 6, а нивои гнездења нејаснији су него што помажу читљивости структуре функције (верност према доље 3).

У верзији ЕС5, пипе () заузима већину тела функције - толико да је помало сулудо да се дефинише у линији. Доиста је потребно разбити у засебну функцију да би се верзија ЕС5 учинила читљивом:

вар пипе = функција () {
  вар фнс = [] .слице.цалл (аргументи);
  повратна функција (к) {
    ретурн фнс.редуце (функција (ацц, фн) {
      ретурн фн (ацц);
    }, Икс);
  };
};
вар цомпосеМикинс = фунцтион () {
  вар микинс = [] .слице.цалл (аргументи);
  функција повратка (примера, микс) {
    иф (! инстанце) инстанца = {};
    иф (! мик) мик = цев;
    повратак мик.аппли (нулл, микинс) (примерак);
  };
};

То ми се чини очигледније читљивим и разумљивим.

Да видимо шта се дешава када на ЕС6 верзију применимо исту „оптимизацију“ читљивости:

цонст пипе = (... фнс) => к => фнс.редуце ((ацц, фн) => фн (ацц), к);
цонст цомпосеМикинс = (... микинс) => (
  инстанце = {},
  мик = цев
) => мик (... микинс) (примерак);

Као и ЕС5 оптимизација, ова верзија је вишеструка (додаје нову променљиву која раније није била). За разлику од верзије ЕС5, ова верзија није значајно читљивија након апстракције дефиниције цеви. Напокон је већ имао назив променљиве који му је јасно додељен у потпису функције: мик.

Дефиниција микса је већ била садржана у сопственом ретку, због чега је мало вероватно да ће се читаоци збунити где се завршава, а остатак функције се наставља.

Сада имамо две варијабле које представљају исту ствар уместо 1. Да ли смо стекли много? Није очигледно, не.

Па зашто је ЕС5 верзија очигледно боља са истим функцијама?

Јер је верзија ЕС5 очигледно сложенија. Извор те сложености је срж ове материје. Тврдим да се извор сложености своди на синтаксни шум и да синтакса шума замрачује значење функције, а не помаже.

Померимо зупчанике и елиминишемо још неке променљиве. Користимо ЕС6 за оба примера и упоредимо само функције стрелице са наслијеђеним изразима функција:

вар цомпосеМикинс = функција (... микинс) {
  повратна функција (
    инстанце = {},
    мик = функција (... фнс) {
      повратна функција (к) {
        ретурн фнс.редуце (функција (ацц, фн) {
          ретурн фн (ацц);
        }, Икс);
      };
    }
  ) {
    повратни микс (... микинс) (примерак);
  };
};

Ово ми изгледа значајно читљивије. Све што смо променили јесте да користимо предност синтаксе параметара одмора и подразумеваних параметара. Наравно, мораћете бити упознати са синтаксом одмора и подразумеваном како би ова верзија била читљивија, али чак и ако нисте, мислим да је очигледно да је ова верзија још увек мање скучена.

То је пуно помогло, али и даље ми је јасно да је ова верзија још увек толико скучена да би апстрахирање цеви () у своју функцију очигледно помогло:

цонст цијев = функција (... фнс) {
  повратна функција (к) {
    ретурн фнс.редуце (функција (ацц, фн) {
      ретурн фн (ацц);
    }, Икс);
  };
};
// Легаци функције израза
цонст цомпосеМикинс = функција (... микинс) {
  повратна функција (
    инстанце = {},
    мик = цев
  ) {
    повратни микс (... микинс) (примерак);
  };
};

То је боље, зар не? Сада, када додјељивање микса заузима само једну линију, структура функције је много јаснија - али и даље постоји превише синтакса по мом укусу. У цомпосеМикинс (), на први поглед ми није јасно где се једна функција завршава, а друга почиње.

Уместо да позива функције функција, чини се да се ова кључна реч визуелно стапа са идентификаторима око ње. Постоје функције које се крију у мојој функцији! Гдје започиње потпис потписа и тијело функције? Могу схватити ако гледам изблиза, али мени то није визуелно очигледно.

Шта ако се можемо ослободити кључне ријечи функције и позвати повратне вриједности визуално их показујући великом стрелицом масти => умјесто да напишемо повратну кључну ријеч која се стапа са околним идентификаторима?

Испада, можемо, а ево како то изгледа:

цонст цомпосеМикинс = (... микинс) => (
  инстанце = {},
  мик = цев
) => мик (... микинс) (примерак);

Сада би требало бити јасно шта се догађа. цомпосеМикинс () је функција која узима било који број микина и враћа функцију која узима два опционална параметра, инстанце и микс. Враћа резултат примене цевовода кроз састављене миксене.

Само још једна ствар… ако исту оптимизацију применимо на пипе (), она се магично трансформише у једносмерни слој:

цонст пипе = (... фнс) => к => фнс.редуце ((ацц, фн) => фн (ацц), к);

С том дефиницијом у једној линији, предност апстракције на сопствену функцију је мање јасна. Запамтите, ова функција постоји као услужни програм у Лодасху, Рамди и гомилу других библиотека, али да ли заиста вреди потрошити велики број увоза друге библиотеке?

Да ли га уопште вриједи извући у своју линију? Вероватно. Они су заиста двије различите функције, а њихово раздвајање то чини јаснијим.

С друге стране, његово укључивање појашњава врсту и очекивања у погледу употребе када погледате потпис параметра. Ево шта се дешава када га креирамо у линији:

цонст цомпосеМикинс = (... микинс) => (
  инстанце = {},
  мик = (... фнс) => к => фнс.редуце ((ацц, фн) => фн (ацц), к)
) => мик (... микинс) (примерак);

Сада смо се вратили оригиналној функцији. Уз пут, нисмо одбацили никакво значење. У ствари, проглашавањем наших параметара и заданих вредности уграђеним, додали смо информације о томе како се функција користи и како вредности параметара могу изгледати.

Сав тај додатни код у ЕС5 верзији био је само шум. Синтак шум. То није служило никаквој корисној сврси, осим за аклиматизацију људи који нису упознати са функцијама стрелице.

Једном када стекнете довољно познавања функција закривљених стрелица, требало би бити јасно да је оригинална верзија читљивија, јер постоји много мање синтаксе у коју се можете изгубити.

Такође је мање подложна грешкама, јер постоји много мања површина за грешке у којој се могу сакрити.

Претпостављам да постоји пуно грешака који се крију у наслијеђеним функцијама и који би били пронађени и елиминирани ако надоградите на функције стрелице.

Претпостављам и да би ваш тим постао значајно продуктивнији ако бисте научили да прихватате и фаворизирате више концизних синтакса доступних у ЕС6.

Иако је тачно да је понекад ствари лакше разумети ако су оне експлицитне, такође је истина да је мање кода боље.

Ако мање кода може постићи исту ствар и комуницирати више, а да притом не жртвује никакво значење, то је објективно боље.

Кључ спознаје разлике је значење. Ако више кода не дода више значења, тај код не треба да постоји. Тај концепт је толико базичан, да је добро позната стилска смјерница за природни језик.

Иста смјерница стила односи се на изворни код. Пригрлите је и код ће вам бити бољи.

На крају дана, светло у тами. Као одговор на још један твит који каже да је верзија ЕС6 мање читљива:

Време је да се упознате са ЕС6, саставом цурриинг-а и функцијом.

Следећи кораци

Чланови „Леарн ЈаваСцрипт витх Ериц Еллиотт“ могу тренутно да гледају 55-минутну лекцију Цурри & Цомпоситион ЕС6.

Ако нисте члан, недостаје вам!

Ериц Еллиотт је аутор програма „Програмирање ЈаваСцрипт апликација“ (О’Реилли) и „Научите ЈаваСцрипт са Ерицом Еллиоттом“. Доприносило је софтверском искуству за Адобе Системс, Зумба Фитнесс, Тхе Валл Стреет Јоурнал, ЕСПН, ББЦ и врхунске уметнике за снимање, укључујући Усхер, Франк Оцеан, Металлицу и многе друге.

Већину свог времена проводи у пределу залива Сан Франциско са најљепшом женом на свијету.