De introductie van een functioneel programmeur voor JavaScript (software voor het componeren)

Smoke Art Cubes to Smoke - MattysFlicks - (CC BY 2.0)
Opmerking: dit maakt deel uit van de serie "Composing Software" (nu een boek!) Over het leren van functionele programmeer- en compositiesoftwaretechnieken in JavaScriptES6 + vanaf het begin. Blijf kijken. Er komt nog veel meer aan!
Koop het boek | Index |

Voor degenen die niet bekend zijn met JavaScript of ES6 +, is dit bedoeld als een korte introductie. Of u nu een beginner of een ervaren JavaScript-ontwikkelaar bent, u kunt iets nieuws leren. Het volgende is alleen bedoeld om het oppervlak te bekrassen en je enthousiast te maken. Als je meer wilt weten, zul je gewoon dieper moeten verkennen. Er staat nog veel meer te wachten.

De beste manier om te leren coderen is coderen. Ik raad je aan om mee te werken met een interactieve JavaScript-programmeeromgeving zoals CodePen of de Babel REPL.

Als alternatief kunt u wegkomen met het gebruik van de REPL's van de Node of de browserconsole.

Uitdrukkingen en waarden

Een uitdrukking is een stuk code dat een waarde oplevert.

De volgende zijn alle geldige uitdrukkingen in JavaScript:

7;
7 + 1; // 8
7 * 2; // 14
'Hallo'; // Hallo

De waarde van een uitdrukking kan een naam krijgen. Wanneer u dit doet, wordt de uitdrukking eerst geëvalueerd en wordt de resulterende waarde toegewezen aan de naam. Hiervoor gebruiken we het sleutelwoord const. Het is niet de enige manier, maar het is degene die je het meest zult gebruiken, dus we blijven voorlopig bij const:

const hello = 'Hallo';
Hallo; // Hallo

var, let en const

JavaScript ondersteunt nog twee variabele declaratie sleutelwoorden: var en let. Ik denk graag aan hen in volgorde van selectie. Standaard selecteer ik de strengste verklaring: const. Een variabele gedeclareerd met het sleutelwoord const kan niet opnieuw worden toegewezen. De definitieve waarde moet worden toegekend op het moment van aangifte. Dit klinkt misschien rigide, maar de beperking is een goede zaak. Het is een signaal dat zegt: "de waarde die aan deze naam wordt toegewezen, zal niet veranderen". Het helpt u om volledig te begrijpen wat de naam meteen betekent, zonder de hele functie te hoeven lezen of het bereik te blokkeren.

Soms is het handig om variabelen opnieuw toe te wijzen. Als u bijvoorbeeld handmatige, imperatieve iteratie gebruikt in plaats van een meer functionele benadering, kunt u een met let toegewezen teller herhalen.

Omdat var je het minst vertelt over de variabele, is dit het zwakste signaal. Sinds ik ES6 ben gaan gebruiken, heb ik nooit opzettelijk een var in een echt softwareproject verklaard.

Houd er rekening mee dat zodra een variabele met let of const wordt gedeclareerd, elke poging om deze opnieuw te declareren een fout tot gevolg heeft. Als u de voorkeur geeft aan meer experimentele flexibiliteit in de REPL-omgeving (lezen, evalueren, afdrukken), kunt u var in plaats van const gebruiken om variabelen te declareren. Var opnieuw aangeven is toegestaan.

In deze tekst wordt const gebruikt om je de gewoonte te geven om const voor standaardprogramma's in gebreke te blijven, maar voel je vrij om var te vervangen door interactieve experimenten.

Types

Tot nu toe hebben we twee typen gezien: getallen en tekenreeksen. JavaScript heeft ook booleans (waar of onwaar), arrays, objecten en meer. We komen later op andere typen.

Een array is een geordende zoeklijst. Zie het als een doos die veel items kan bevatten. Hier is de letterlijke notatie van de array:

[1, 2, 3];

Dat is natuurlijk een uitdrukking die een naam kan krijgen:

const arr = [1, 2, 3];

Een object in JavaScript is een verzameling sleutel: waardeparen. Het heeft ook een letterlijke notatie:

{
  sleutel waarde'
}

En natuurlijk kunt u een object aan een naam toewijzen:

const foo = {
  bar: 'bar'
}

Als u bestaande variabelen wilt toewijzen aan objecteigenschappen met dezelfde naam, is daar een snelkoppeling voor. U kunt gewoon de variabelenaam typen in plaats van zowel een sleutel als een waarde op te geven:

const a = 'a';
const oldA = {a: a}; // lange, overbodige manier
const oA = {a}; // kort en krachtig!

Laten we het gewoon nog een keer doen:

const b = 'b';
const oB = {b};

Objecten kunnen eenvoudig worden samengevoegd tot nieuwe objecten:

const c = {... oA, ... oB}; // {a: 'a', b: 'b'}

Die punten zijn de objectspreidingsoperator. Het herhaalt de eigenschappen in oA en wijst deze toe aan het nieuwe object, doet vervolgens hetzelfde voor oB en vervangt alle sleutels die al op het nieuwe object bestaan. Vanaf dit moment is objectspreiding een nieuwe, experimentele functie die mogelijk nog niet in alle populaire browsers beschikbaar is, maar als het niet voor u werkt, is er een vervanging: Object.assign ():

const d = Object.assign ({}, oA, oB); // {a: 'a', b: 'b'}

Slechts een beetje meer typen in het voorbeeld Object.assign (), en als u veel objecten componeert, kan dit u zelfs wat typen besparen. Merk op dat wanneer u Object.assign () gebruikt, u een doelobject moet doorgeven als de eerste parameter. Het is het object waarnaar eigenschappen worden gekopieerd. Als u het doelobject vergeet en weglaat, wordt het object dat u in het eerste argument doorgeeft gemuteerd.

Mijn ervaring is dat het muteren van een bestaand object in plaats van het maken van een nieuw object meestal een bug is. Het is op zijn minst foutgevoelig. Wees voorzichtig met Object.assign ().

destructurering

Zowel objecten als arrays ondersteunen destructurering, wat betekent dat u er waarden uit kunt extraheren en ze kunt toewijzen aan benoemde variabelen:

const [t, u] = ['a', 'b'];
t; // 'een'
u; // 'b'
const blep = {
  blop: 'blop'
};

// Het volgende komt overeen met:
// const blop = blep.blop;
const {blop} = blep;
blop, // 'blop'

Net als bij het bovenstaande matrixvoorbeeld, kunt u meerdere opdrachten tegelijk vernietigen. Hier is een regel die je in veel Redux-projecten zult zien:

const {type, payload} = actie;

Hier is hoe het wordt gebruikt in de context van een verloopstuk (veel meer over dat onderwerp komt later):

const myReducer = (state = {}, action = {}) => {
  const {type, payload} = actie;
  schakelaar (type) {
    case 'FOO': retour Object.assign ({}, status, payload);
    standaard: retourstatus;
  }
};

Als u geen andere naam voor de nieuwe binding wilt gebruiken, kunt u een nieuwe naam toewijzen:

const {blop: bloop} = blep;
bloop; // 'blop'

Lezen: blep.blop toewijzen als bloop.

Vergelijkingen en Ternaries

U kunt waarden vergelijken met de operator voor strikte gelijkheid (ook wel "drievoudige gelijken" genoemd):

3 + 1 === 4; // waar

Er is ook een slordige operator voor gelijkheid. Het staat formeel bekend als de 'Gelijke' operator. Informeel, "dubbel is gelijk aan". Dubbel gelijk aan heeft een geldige use-case of twee, maar het is bijna altijd beter om standaard de operator === in te schakelen.

Andere vergelijkingsoperatoren zijn onder meer:

  • > Groter dan
  • > = Groter dan of gelijk aan
  • <= Kleiner dan of gelijk aan
  • ! = Niet gelijk
  • ! == Niet strikt gelijk
  • && Logisch en
  • || Logisch of

Een drievoudige uitdrukking is een uitdrukking waarmee u een vraag kunt stellen met behulp van een vergelijker en evalueert naar een ander antwoord, afhankelijk van of de uitdrukking waarheidsgetrouw is:

14 - 7 === 7? 'Yep!' : 'Nee.'; // Ja!

functies

JavaScript heeft functie-expressies, die aan namen kunnen worden toegewezen:

const dubbel = x => x * 2;

Dit betekent hetzelfde als de wiskundige functie f (x) = 2x. Hardop gesproken, die functie leest f van x is gelijk aan 2x. Deze functie is alleen interessant wanneer u deze toepast op een specifieke waarde van x. Om de functie in andere vergelijkingen te gebruiken, zou je f (2) schrijven, wat dezelfde betekenis heeft als 4.

Met andere woorden, f (2) = 4. Je kunt een wiskundige functie beschouwen als een afbeelding van ingangen naar uitgangen. f (x) is in dit geval een toewijzing van invoerwaarden voor x aan overeenkomstige uitvoerwaarden gelijk aan het product van de invoerwaarde en 2.

In JavaScript is de waarde van een functie-uitdrukking de functie zelf:

dubbele; // [Functie: dubbel]

U kunt de functiedefinitie bekijken met de methode .toString ():

double.toString (); // 'x => x * 2'

Als u een functie op sommige argumenten wilt toepassen, moet u deze aanroepen met een functie-aanroep. Een functieaanroep past een functie toe op zijn argumenten en evalueert deze naar een retourwaarde.

U kunt een functie aanroepen met (argument1, argument2, ... rest). Om bijvoorbeeld onze dubbele functie op te roepen, voegt u gewoon de haakjes toe en geeft u een waarde in om te verdubbelen:

double (2); // 4

In tegenstelling tot sommige functionele talen zijn die haakjes zinvol. Zonder hen wordt de functie niet genoemd:

dubbel 4; // SyntaxError: onverwacht nummer

handtekeningen

Functies hebben handtekeningen, die bestaan ​​uit:

  1. Een optionele functienaam.
  2. Een lijst met parametertypen, tussen haakjes. De parameters kunnen optioneel worden genoemd.
  3. Het type retourwaarde.

Type handtekeningen hoeven niet te worden opgegeven in JavaScript. De JavaScript-engine zal de typen tijdens runtime uitzoeken. Als u voldoende aanwijzingen geeft, kan de handtekening ook worden afgeleid door ontwikkelaarstools zoals IDE's (Integrated Development Environment) en Tern.js met behulp van gegevensstroomanalyse.

JavaScript mist zijn eigen functiehandtekeningnotatie, dus er zijn een paar concurrerende standaarden: JSDoc is historisch zeer populair geweest, maar het is onhandig uitgebreid, en niemand stoort om de doc-reacties up-to-date te houden met de code, zoveel JS-ontwikkelaars hebben stopte ermee het te gebruiken.

TypeScript en Flow zijn momenteel de grote kanshebbers. Ik weet niet zeker hoe ik alles moet uitdrukken in een van beide, dus ik gebruik Rtype, alleen voor documentatiedoeleinden. Sommige mensen vallen terug op Haskell's alleen Hindley-Milner-types met curry. Ik zou graag een goed notatiesysteem willen zien dat gestandaardiseerd is voor JavaScript, al was het alleen voor documentatiedoeleinden, maar ik denk niet dat een van de huidige oplossingen op dit moment voldoende is. Voor nu, scheel en doe je best om de rare typeaanduidingen bij te houden die er waarschijnlijk iets anders uitzien dan wat je ook gebruikt.

functionName (param1: Type, param2: Type) => Type

De handtekening voor dubbel is:

dubbel (x: n) => getal

Ondanks het feit dat JavaScript niet vereist dat handtekeningen worden geannoteerd, is het belangrijk om te weten wat handtekeningen zijn en wat ze betekenen, om efficiënt te communiceren over hoe functies worden gebruikt en hoe functies zijn samengesteld. Voor de meeste herbruikbare functies voor functiesamenstelling moet u functies doorgeven die dezelfde typeaanduiding delen.

Standaard parameterwaarden

JavaScript ondersteunt standaard parameterwaarden. De volgende functie werkt als een identiteitsfunctie (een functie die dezelfde waarde retourneert die u doorgeeft), tenzij u deze aanroept met ongedefinieerd of gewoon helemaal geen argument doorgeeft - dan retourneert deze nul:

const orZero = (n = 0) => n;

Om een ​​standaard in te stellen, wijst u deze eenvoudig toe aan de parameter met de operator = in de functiesignatuur, zoals in n = 0, hierboven. Wanneer u op deze manier standaardwaarden toewijst, kunnen type interferentietools zoals Tern.js, Flow of TypeScript de typeaanduiding van uw functie automatisch afleiden, zelfs als u niet expliciet typeannotaties declareert.

Het resultaat is dat, met de juiste plug-ins geïnstalleerd in uw editor of IDE, u de functiehandtekeningen inline kunt zien terwijl u functieoproepen typt. U zult ook in één oogopslag kunnen begrijpen hoe u een functie kunt gebruiken op basis van de roepnaam. Het gebruik van standaardtoewijzingen waar dit zinvol is, kan u helpen meer zelfdocumenterende code te schrijven.

Opmerking: parameters met standaardwaarden tellen niet mee voor de eigenschap .length van de functie, waardoor hulpprogramma's zoals autocurry worden gegenereerd die afhankelijk zijn van de waarde .length. Met sommige curryhulpprogramma's (zoals lodash / curry) kunt u een aangepaste arity passeren om deze beperking te omzeilen als u er tegenaan loopt.

Genoemde argumenten

JavaScript-functies kunnen objectliteralen als argumenten gebruiken en destructureringsopdrachten in de parameterhandtekening gebruiken om het equivalent van benoemde argumenten te bereiken. Let op, u kunt ook standaardwaarden toewijzen aan parameters met behulp van de standaardparameterfunctie:

const createUser = ({
  name = 'Anoniem',
  avatarThumbnail = '/avatars/anonymous.png'
}) => ({
  naam,
  avatarThumbnail
});
const george = createUser ({
  naam: 'George',
  avatarThumbnail: 'avatars / shades-emoji.png'
});
George;
/ *
{
  naam: 'George',
  avatarThumbnail: 'avatars / shades-emoji.png'
}
* /

Rust en verspreid

Een gemeenschappelijk kenmerk van functies in JavaScript is de mogelijkheid om een ​​groep resterende argumenten in de functiesignatuur te verzamelen met behulp van de restoperator: ...

De volgende functie negeert bijvoorbeeld eenvoudig het eerste argument en retourneert de rest als een array:

const aTail = (head, ... tail) => tail;
aStaart (1, 2, 3); // [2, 3]

Rest verzamelt afzonderlijke elementen samen in een array. Spread doet het tegenovergestelde: het verspreidt de elementen van een array naar afzonderlijke elementen. Overweeg dit:

const shiftToLast = (kop, ... staart) => [... staart, kop];
shiftToLast (1, 2, 3); // [2, 3, 1]

Arrays in JavaScript hebben een iterator die wordt aangeroepen wanneer de spread-operator wordt gebruikt. Voor elk item in de array levert de iterator een waarde. In de uitdrukking, [... staart, kop], kopieert de iterator elk element in volgorde van de staartmatrix naar de nieuwe matrix gecreëerd door de omringende letterlijke notatie. Omdat het hoofd al een individueel element is, ploffen we het gewoon aan het einde van de reeks en zijn we klaar.

Currying

Een gecurryde functie is een functie die meerdere parameters één voor één gebruikt: er is een parameter nodig en deze retourneert een functie die de volgende parameter aanneemt, enzovoort totdat alle parameters zijn opgegeven, op welk punt de toepassing is voltooid en de definitieve waarde wordt geretourneerd.

Curry en gedeeltelijke toepassing kunnen worden ingeschakeld door een andere functie te retourneren:

const highpass = cutoff => n => n> = cutoff;
const gt4 = highpass (4); // highpass () retourneert een nieuwe functie

U hoeft geen pijlfuncties te gebruiken. JavaScript heeft ook een functiezoekwoord. We gebruiken pijlfuncties omdat het functietrefwoord veel meer typen is. Dit komt overeen met de definitie highPass () hierboven:

const highpass = functie highpass (cutoff) {
  retourfunctie (n) {
    return n> = cutoff;
  };
};

De pijl in JavaScript betekent ongeveer "functie". Er zijn enkele belangrijke verschillen in functiegedrag, afhankelijk van het soort functie dat u gebruikt (=> heeft dit niet en kan niet als constructor worden gebruikt), maar we komen bij die verschillen aan wanneer we daar aankomen. Voor nu, als je x => x ziet, denk dan "een functie die x neemt en x retourneert". Dus je kunt const highpass = cutoff => n => n> = cutoff lezen; zoals:

"Highpass is een functie die cutoff neemt en een functie retourneert die n neemt en het resultaat van n> = cutoff retourneert".

Omdat highpass () een functie retourneert, kunt u deze gebruiken om een ​​meer gespecialiseerde functie te maken:

const gt4 = highpass (4);
GT4 (6); // waar
GT4 (3); // onwaar

Met Autocurry kunt u functies automatisch curryen, voor maximale flexibiliteit. Stel dat u een functie add3 () hebt:

const add3 = curry ((a, b, c) => a + b + c);

Met autocurry kun je het op verschillende manieren gebruiken en het zal het juiste retourneren, afhankelijk van hoeveel argumenten je doorgeeft:

add3 (1, 2, 3); // 6
add3 (1, 2) (3); // 6
add3 (1) (2, 3); // 6
ADD3 (1) (2) (3); // 6

Sorry, Haskell-fans, JavaScript mist een ingebouwd autocurry-mechanisme, maar je kunt er een importeren vanuit Lodash:

$ npm install - sla lodash op

Vervolgens, in uw modules:

curry importeren uit 'lodash / curry';

Of u kunt de volgende magische spreuk gebruiken:

// Kleine, recursieve autocurry
const curry = (
  f, arr = []
) => (... args) => (
  a => a.length === f.length?
    f (... a):
    curry (f, a)
) ([... arr, ... args]);

Functie samenstelling

Natuurlijk kun je functies samenstellen. Functiesamenstelling is het proces waarbij de retourwaarde van de ene functie als argument wordt doorgegeven aan een andere functie. In wiskundige notatie:

f. g

Dit vertaalt zich in JavaScript:

f (g (x))

Het wordt van binnenuit geëvalueerd:

  1. x wordt geëvalueerd
  2. g () wordt toegepast op x
  3. f () wordt toegepast op de retourwaarde van g (x)

Bijvoorbeeld:

const inc = n => n + 1;
inc (double (2)); // 5

De waarde 2 wordt doorgegeven in double (), wat 4 oplevert. 4 wordt doorgegeven aan inc () die 5 oplevert.

U kunt elke uitdrukking als argument doorgeven aan een functie. De uitdrukking wordt geëvalueerd voordat de functie wordt toegepast:

inc (dubbel (2) * dubbel (2)); // 17

Omdat dubbel (2) resulteert in 4, kun je dat lezen als inc (4 * 4) dat resulteert in inc (16) dat vervolgens resulteert in 17.

Functiesamenstelling staat centraal bij functioneel programmeren. We zullen er later veel meer over hebben.

arrays

Arrays hebben een aantal ingebouwde methoden. Een methode is een functie die is gekoppeld aan een object: meestal een eigenschap van het bijbehorende object:

const arr = [1, 2, 3];
arr.map (double); // [2, 4, 6]

In dit geval is arr het object, is .map () een eigenschap van het object met een functie voor een waarde. Wanneer u het oproept, wordt de functie toegepast op de argumenten, evenals een speciale parameter genaamd deze, die automatisch wordt ingesteld wanneer de methode wordt aangeroepen. De waarde this is hoe .map () toegang krijgt tot de inhoud van de array.

Merk op dat we de dubbele functie als een waarde doorgeven aan de kaart in plaats van deze aan te roepen. Dat komt omdat map een functie als argument neemt en deze op elk item in de array toepast. Het retourneert een nieuwe array met de waarden die worden geretourneerd door double ().

Merk op dat de oorspronkelijke arr-waarde ongewijzigd is:

arr; // [1, 2, 3]

Methode Chaining

U kunt ook methodeaanroepen koppelen. Methodeketting is het proces waarbij een methode rechtstreeks wordt aangeroepen op de retourwaarde van een functie, zonder dat de naam op naam moet worden geretourneerd:

const arr = [1, 2, 3];
arr.map (double) .map (double); // [4, 8, 12]

Een predikaat is een functie die een booleaanse waarde retourneert (waar of onwaar). De methode .filter () gebruikt een predikaat en retourneert een nieuwe lijst, waarbij alleen de items worden geselecteerd die het predicaat passeren (true retourneren) om in de nieuwe lijst te worden opgenomen:

[2, 4, 6]. Filter (gt4); // [4, 6]

Vaak wilt u items uit een lijst selecteren en deze items vervolgens toewijzen aan een nieuwe lijst:

[2, 4, 6] .filter (gt4) .map (dubbel); [8, 12]

Opmerking: Later in deze tekst ziet u een efficiëntere manier om tegelijkertijd iets te selecteren en toe te wijzen met behulp van een transducer, maar er zijn nog andere dingen die eerst moeten worden onderzocht.

Conclusie

Maak je geen zorgen als je hoofd nu draait. We hebben nauwelijks het oppervlak van veel dingen bekrast die veel meer onderzoek en overweging verdienen. We zullen teruggaan en enkele van deze onderwerpen binnenkort veel dieper verkennen.

Koop het boek | Index |

Meer informatie op EricElliottJS.com

Videolessen met interactieve code-uitdagingen zijn beschikbaar voor leden van EricElliottJS.com. Als u geen lid bent, meld u dan vandaag nog aan.

Eric Elliott is een gedistribueerde systeemexpert en auteur van de boeken, "Composing Software" en "Programming JavaScript Applications". Als mede-oprichter van DevAnywhere.io leert hij ontwikkelaars de vaardigheden die ze nodig hebben om op afstand te werken en een balans te vinden tussen werk en privé. Hij bouwt en adviseert ontwikkelingsteams voor cryptoprojecten en heeft bijgedragen aan software-ervaringen voor Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC en topartiesten zoals Usher, Frank Ocean, Metallica en nog veel meer.

Hij geniet van een afgelegen levensstijl met de mooiste vrouw ter wereld.