Toiminnalliset ohjelmointimallit: keittokirja

Kuva Jonas Jacobsson Unsplash-kuvassa

Tämä artikkeli on tarkoitettu yleisölle, joka on valmistunut toiminnallisista kirjastoista, kuten ramda, algebrallisten tietotyyppien käyttöön. Käytämme erinomaista krokkikirjastoa ADT-sovittajillemme ja avustajillemme, vaikka nämä käsitteet voivat koskea myös muita. Keskitymme osoittamaan käytännön sovelluksia ja malleja ravittamatta paljon teoriaa.

Vaarallisten toimintojen turvallinen suorittaminen

Oletetaan, että meillä on tilanne, jossa haluamme käyttää pimeäksi kutsuttua toimintoa kolmannen osapuolen kirjastosta. tummentaa kerroimen, värin ja palauttaa tumman sävyn.

Melko kätevä CSS-tarpeidemme kannalta. Mutta osoittautuu, että toiminto ei ole niin viaton kuin miltä näyttää. pimeä heittää virheitä, kun se saa odottamattomia argumentteja!

Tämä on tietysti erittäin hyödyllinen virheenkorjauksessa - mutta emme haluaisi, että sovelluksemme räjähtää vain siksi, että emme pystyneet saamaan väriä. TryCatch tulee tässä auttamaan.

tryCatch suorittaa toimitetun toiminnon try-catch-lohkossa ja palauttaa summatyypin, jota kutsutaan tulokseksi. Summatyyppi on pohjimmiltaan "tai" -tyyppi. Tämä tarkoittaa, että tulos voi olla joko ok, jos toiminto onnistuu, tai virhe, jos vikaantuminen. Muita esimerkkejä summatyypeistä ovat Ehkä, Joko, Async ja niin edelleen. Kumpikin pistemätön avustaja hajottaa arvon Tulos-ruudusta ja palauttaa CSS-oletusperinnön, jos asiat menivät etelään, tai tummennetun värin, jos kaikki meni hyvin.

Tyyppien täytäntöönpano ehkä auttajien avulla

JavaScriptin avulla törmäämme usein tapauksiin, joissa toiminnot räjähtävät, koska odotamme tiettyä tietotyyppiä, mutta saamme sen sijaan toisen. crocks tarjoaa turvalliset, safeAfter- ja safeLift-toiminnot, joiden avulla voimme suorittaa koodia ennakoitavammin Ehkä-tyyppiä käyttämällä. Katsotaanpa tapaa muuntaa camelCased-jouset otsikkokoteloksi.

Olemme luoneet auttajafunktiotuloksen, joka käyttää safeAfter-sovellusta String.prototype.match -käyttäytymisen palauttamiseen määrittelemätöntä käyttämättä, jos vastaavia ei ole. IsArray-predikaatti varmistaa, että saamme mitään, jos vastaavia ei löydy, ja vain [merkkijono], jos otteluita löytyy. safeAfter sopii erinomaisesti nykyisten tai kolmansien osapuolien toimintojen suorittamiseen luotettavalla ja turvallisella tavalla.

(Vinkki: safeAfter toimii todella hyvin ramda-toiminnoilla, jotka palauttavat | määrittelemättömän.)

Kamelisoimaton -toiminto suoritetaan safeLift (isString) -toiminnolla, mikä tarkoittaa, että se suoritetaan vasta, kun tulo palaa totta isString-predikaatille.

Tämän lisäksi crocks tarjoaa myös prop ja propPath-auttajia, joiden avulla voit valita ominaisuuksia esineistä ja ryhmistä.

Tämä on hienoa, varsinkin jos käsittelemme tietoja sivuvaikutuksista, jotka eivät ole hallinnassamme, kuten API-vastaukset. Mutta mitä tapahtuu, jos sovellusliittymäkehittäjät päättävät yhtäkkiä käsitellä muotoilua lopussa?

Suorituksenaikaiset virheet! Yritimme vedota toFixed-menetelmään merkkijonoon, jota ei oikeastaan ​​ole. Meidän on varmistettava, että bankBalance on todella numero, ennen kuin vedomme siihen kiinnitettyihin. Yritetään ratkaista se turvallisella auttajalla.

Putkistamme prop-toiminnon tulokset turvalliseen (isNumber) -toimintoomme, joka myös palauttaa Ehkä sen mukaan, tyydyttääkö prop-tulos predikaatin. Yllä oleva putkilinja takaa, että viimeistä karttaa, joka sisältää kiinnitetyn, kutsutaan vasta, kun bankBalance on numero.

Jos aiot käsitellä paljon samanlaisia ​​tapauksia, on järkevää poimia tämä ohje auttajana:

Sovellusten käyttö toimintojen pitämiseksi puhtaina

Usein olemme tilanteissa, joissa haluamme käyttää olemassa olevaa toimintoa, jonka arvot on kääritty astiaan. Yritetään suunnitella turvallinen lisäystoiminto, joka sallii vain numerot, käyttämällä edellisen osan käsitteitä. Tässä on ensimmäinen yritys.

Tämä tekee tarkalleen mitä tarvitsemme, mutta lisäystoiminto ei ole enää yksinkertainen a + b. Sen on ensin nostettava arvomme Maybesiin, saavutettava niihin pääsemiseksi arvoihin ja palautettava sitten tulos. Meidän on löydettävä tapa säilyttää lisätoiminnomme ydintoiminnot, samalla kun annamme sen toimia ADT: ien sisältämien arvojen kanssa! Sovellettavat toimijat ovat hyödyllisiä tässä.

Sovellettava Functor on kuin tavallinen functor, mutta kartan lisäksi se toteuttaa myös kaksi ylimääräistä menetelmää:

/: Soveltava f => a -> f a

The on täysin tyhmä rakentaja, ja nostaa arvon, jonka annat sille, tietotyyppimme. Sitä kutsutaan myös puhtaana muilla kielillä.

Ja tässä kaikki raha on - ap-menetelmä:

ap: Levitä f => f a ~> f (a -> b) -> f b

Allekirjoitus näyttää hyvin samanlaiselta kuin kartta, ainoana erona on, että a -> b-funktiomme on myös kääritty f: ään. Katsotaanpa tämä toiminnassa.

Nostamme ensin curried add -toiminnomme Ehkäksi ja sitten soveltamme Ehkä a ja Ehkä b siihen. Olemme käyttäneet karttaa toistaiseksi kontin sisällä olevan arvon käyttämiseen, ja ap ei eroa toisistaan. Sisäisesti se kartoittaa safeNumber (a) -sovelluksen käyttääksesi a: ta ja soveltaa sitä lisätäkseen. Tuloksena on Ehkä, joka sisältää osittain sovelletun lisäyksen. Toistamme saman prosessin safeNumber (b) -toiminnolla lisäystoiminnon suorittamiseksi, jolloin saadaan tuloksena Just-arvo, jos molemmat a ja b ovat kelvollisia tai ei mitään.

Crocks tarjoaa meille myös liftA2- ja liftN-apulaitteet ilmaisemaan samaa käsitettä pointless-tavalla. Seuraava on triviaali esimerkki:

Käytämme tätä auttajaa laajasti Parallelismia ilmaisevassa osiossa.

Vinkki: Koska olemme havainneet, että ap käyttää arvoja karttan avulla, voimme tehdä hienoja asioita, kuten luoda Cartesian-tuotteen, kun heille annetaan kaksi luetteloa.

Asyncin käyttäminen ennustettavissa olevaan virheiden käsittelyyn

crocks tarjoaa Asyncin tietotyypin, jonka avulla voimme rakentaa laiskoja asynkronisia laskelmia. Jos haluat tietää enemmän siitä, katso täältä laajat viralliset asiakirjat. Tämän osan tavoitteena on tarjota esimerkkejä siitä, kuinka voimme käyttää Asynciä virheiden ilmoittamisen laadun parantamiseksi ja koodimme joustavuuden parantamiseksi.

Usein törmäämme tapauksiin, joissa haluamme soittaa API-puhelut, jotka ovat riippuvaisia ​​toisistaan. Täällä getUser-päätelaite palauttaa käyttäjäkokonaisuuden GitHubista ja vastaus sisältää paljon sulautettuja URL-osoitteita arkistoihin, tähtiin, suosikkeihin ja niin edelleen. Näemme kuinka voimme suunnitella tämän käyttötavan Asyncin käytön avulla.

MaybeToAsync-muunnoksen käyttö antaa meille mahdollisuuden käyttää kaikkia turvaominaisuuksia, joita saamme Ehkä-käytöstä, ja viedä ne Async-virtaukseemme. Voimme nyt merkitä syöttö- ja muut virheet osana Async-virtaamme.

Monoidien tehokas käyttö

Olemme jo käyttäneet monoideja suorittaessaan toimintoja, kuten jousen / ryhmän liittäminen ja numeroiden lisääminen natiivissa JavaScriptillä. Se on yksinkertaisesti tietotyyppi, joka tarjoaa meille seuraavat menetelmät.

concat :: Monoid m => m a -> m a -> m a

concat antaa meille mahdollisuuden yhdistää kaksi saman tyyppistä monoidia yhdessä ennalta määritetyn toiminnan kanssa.

tyhjä :: monoidi m => () => m a

Tyhjä menetelmä antaa meille identiteettielementin, joka samanaikaisesti muiden saman tyyppisten monoidien kanssa palauttaisi saman elementin. Tässä on mitä puhun.

Se ei sinänsä näytä kovin hyödylliseltä, mutta crocks tarjoaa joitain lisämonoideja yhdessä auttajien mconcat, mreduce, mconcatMap ja mreduceMap kanssa.

Mconcat- ja mreduce-menetelmät ottavat käyttöön monoidin ja luettelon työskenneltävistä elementeistä, ja soveltavat konkotaatteja kaikkiin niiden elementteihin. Ainoa ero niiden välillä on, että mconcat palauttaa monoidin esiintymän, kun taas mreduce palauttaa raaka-arvon. MconcatMap- ja mreduceMap-avustajat toimivat samalla tavalla paitsi, että he hyväksyvät lisätoiminnon, jota käytetään kartoittamaan jokainen elementti ennen kontaktiin soittamista.

Katsotaanpa toista esimerkkiä krokkien monoidista, ensimmäinen monoidi. Liittämisen yhteydessä First palauttaa aina ensimmäisen, ei-tyhjän arvon.

Yritämme ensin luoda toiminto, joka yrittää saada objektista ensimmäisen käytettävissä olevan ominaisuuden.

Aika siisti! Tässä on toinen esimerkki, joka yrittää luoda parhaan mahdollisen muotoilijan, kun tarjotaan erityyppisiä arvoja.

Parallelismin ilmaiseminen pointless-tavalla

Saatamme joutua tapauksiin, joissa haluamme suorittaa useita toimintoja yhdelle tiedolle ja yhdistää tulokset jollain tavalla. crocks tarjoaa meille kaksi tapaa saavuttaa tämä. Ensimmäinen malli hyödyntää tuotetyyppejä Pari ja Tuple. Katsotaanpa pieni esimerkki, jossa meillä on esine, joka näyttää tältä:

{tunnukset: [11233, 12351, 16312], hylkäämiset: [11233]}

Haluamme kirjoittaa funktion, joka hyväksyy tämän objektin ja palauttaa joukon tunnuksia lukuun ottamatta hylättyjä. Ensimmäinen yritys alkuperäisessä JavaScriptissä näyttää tältä:

Tämä tietysti toimii, mutta se räjähtää, jos jokin ominaisuuksista on väärin muotoiltu tai sitä ei ole määritelty. Tehdään getIds-paluuta ehkä sen sijaan. Käytämme fanout-apua, joka hyväksyy kaksi toimintoa, käyttää sitä samalla tulolla ja palauttaa parin tuloksia.

Yksi pointfree-lähestymistavan käytön tärkeimmistä eduista on, että se rohkaisee meitä jakamaan logiikkamme pienempiin osiin. Meillä on nyt uudelleen käytettävä auttajaero (liftA2: n kanssa, kuten aiemmin nähtiin), jonka avulla voimme yhdistää parin molemmat puoliskot.

Toinen menetelmä olisi käyttää konvergoivaa yhdistelmää samanlaisten tulosten saavuttamiseksi. konvergointi vie kolme toimintoa ja tuloarvon. Sitten se käyttää sisääntuloa toiseen ja kolmanteen toimintoon ja putki molempien tulokset ensimmäiseen. Käytämme sitä toiminnon luomiseen, joka normalisoi joukon objekteja niiden tunnusten perusteella. Käytämme Assign Monoid -sovellusta, jonka avulla voimme yhdistää esineitä toisiinsa.

Siirtymisen ja sekvenssin käyttö tietojen terveyden varmistamiseksi

Olemme nähneet kuinka käyttää Ehkä ja ystäviä varmistaaksemme, että työskentelemme aina odotettujen tyyppien kanssa. Mutta mitä tapahtuu, kun työskentelemme tyypin kanssa, joka sisältää muita arvoja, kuten esimerkiksi taulukon tai luettelon? Katsotaanpa yksinkertaista toimintoa, joka antaa meille kaikkien taulukossa olevien merkkijonojen kokonaispituuden.

Loistava. Olemme varmistaneet, että funktiomme antaa aina mitään, jos se ei saa taulukkoa. Riittääkö tämä kuitenkin?

Ei oikeastaan. Tehtävämme ei takaa, että luettelon sisällössä ei ole yllätyksiä. Yksi tapa ratkaista tämä olisi määritellä safeLength-toiminto, joka toimii vain jousilla:

Jos käytämme mappaustoimintoon pituuden sijasta safeLength-arvoa, saamme [Ehkä numero] -kohdan sijaan [Luku], emmekä voi enää käyttää summafunktiota. Tässä on sekvenssi hyödyllinen.

sekvenssi auttaa vaihtamaan sisemmän tyypin ulkoisella tyypillä samalla kun suorittaa tietyn vaikutuksen, koska sisäinen tyyppi on sovellus. Identity-sekvenssi on melko tyhmä - se vain kartoittaa sisätyypin ja palauttaa Identity-säilöön käärityn sisällön. Lista- ja taulukko-sovelluksissa sekvenssi käyttää luettelossa olevaa takaiskua yhdistääksesi sen sisällön käyttämällä ap- ja concat-muotoja. Katsotaanpa tämä käytännössä uudelleenkorjatun kokonaispituuden toteutuksessa.

Loistava! Olemme rakentaneet täysin luodinkestävän kokonaispituuden. Tämä kaava jotain a -> m b: n kartoittamiseksi ja sitten sekvenssin käyttämiseksi on niin yleistä, että meillä on toinen avustaja nimeltään traverse, joka suorittaa molemmat operaatiot yhdessä. Katsotaanpa, kuinka yllä olevassa esimerkissä voidaan käyttää poikittaista järjestyksen sijasta.

Siellä! Se toimii täsmälleen samalla tavalla. Jos ajattelemme sitä, sekvenssioperaattorimme on periaatteessa poikittainen, ja identiteetti on kartoitusfunktio.

Huomaa: Koska emme voi päätellä sisäistä tyyppiä JavaScriptin avulla, meidän on nimenomaisesti toimitettava tyyppirakentaja ensimmäisenä argumenttina kulkemiseen ja sekvenssiin.

On helppo nähdä, kuinka sekvenssi ja kulku ovat arvokkaita tietojen validoinnissa. Yritetään luoda yleinen validoija, joka ottaa kaavan ja vahvistaa syöttöobjektin. Käytämme Tulostyyppiä, joka hyväksyy vasemmalla puolella olevan ryhmän, jonka avulla voimme kerätä virheitä. Semigroup on samanlainen kuin monoid ja se määrittelee concat-menetelmän - mutta toisin kuin monoid, se ei vaadi tyhjän menetelmän olemassaoloa. Esittelemme myös jäljempänä muunnointitoiminnon ehkäToResult, joka auttaa meitä toimimaan Ehkä ja Tulos välillä.

Koska olemme kääntäneet makeValidator-toiminnon tehdäksemme sopivampaa curryointiin, sävellysketjuimme saa kaavion, joka meidän on vahvistettava ensin. Jaamme ensin skeeman avain-arvopareihin ja siirrämme kunkin ominaisuuden arvo sitä vastaavalle validointitoiminnolle. Jos toiminto epäonnistuu, käytämme bimapiä virheen kartoittamiseen, lisäämme siihen lisätietoja ja palautamme sen yksittäisryhmänä. Traverse loppuu sitten kaikki virheet, jos niitä on, tai palauttaa alkuperäisen objektin, jos se on kelvollinen. Voisimme myös palauttaa merkkijonon taulukon sijasta, mutta ryhmä tuntuu paljon mukavammalta.

Kiitos Ian Hofmann-Hicks, Sinisa Louc ja Dale Francis heidän panoksestaan ​​tähän viestiin.