Cum să lucrezi cu înțelegerea Elixir

Elixir este un limbaj de programare foarte mic (a apărut în 2011), dar câștigă popularitate. Am fost inițial interesat de această limbă, deoarece atunci când o folosiți puteți să vă uitați la niște sarcini obișnuite pe care programatorii le rezolvă de obicei dintr-un unghi diferit. De exemplu, puteți afla cum să repetați colecțiile fără pentru ciclu sau cum să vă organizați codul fără clase.

Elixirul are câteva caracteristici foarte interesante și puternice, care ar putea fi greu să-ți faci capul dacă ai venit din lumea OOP. Cu toate acestea, după un timp, totul începe să aibă sens și vedeți cât de expresivă poate fi codul funcțional. Înțelegerile sunt o astfel de caracteristică, iar acest articol vă voi explica cum să lucrați cu ele.

Înțelegeri și cartografiere

În general, o înțelegere a listei este o construcție specială care vă permite să creați o listă nouă pe baza celor existente. Acest concept se găsește în limbi precum Haskell și Clojure. Erlang o prezintă și, prin urmare, Elixir are și înțelegeri.

S-ar putea să întrebați cum sunt diferite înțelegeri față de funcția hartă / 2, care are de asemenea o colecție și produce una nouă? Asta ar fi o întrebare corectă! Ei bine, în cel mai simplu caz, înțelegerile fac aproape același lucru. Aruncați o privire la acest exemplu:

defmodule MyModule do def do_something (listă) nu listă |> Enum.map (fn (el) -> el * 2 sfârșit) sfârșitul sfârșitului MyModule.do_something ([1,2,3]) |> IO.inspect # => 2,4,6]

Aici am pur și simplu o listă cu trei numere și o listă nouă cu toate numerele înmulțite cu 2. Hartă apelul poate fi simplificat în continuare Enum.map (& (& 1 * 2)).

do_something / 1 funcția poate fi acum rescrisă folosind o înțelegere:

 def do_something (listă) pentru el <- list, do: el * 2 end

Aceasta este ceea ce arată o înțelegere de bază și, în opinia mea, codul este un pic mai elegant decât în ​​primul exemplu. Aici, din nou, luăm fiecare element din listă și îl înmulțim cu 2. el <- list partea este numită a generator, și explică exact cum doriți să extrageți valorile din colecția dvs..

Rețineți că nu suntem forțați să transmitem o listă la do_something / 1 funcția-codul va funcționa cu orice este enumerabil:

defmodule MyModule do def do_something (colectare) face pentru el <- collection, do: el * 2 end end MyModule.do_something((1… 3)) |> IO.inspect

În acest exemplu, trec o serie ca argument.

Înțelegerile funcționează și cu binstrenții. Sintaxa este ușor diferită, deoarece trebuie să închideți generatorul << și >>. Să demonstrăm acest lucru prin realizarea unei funcții foarte simple de a "descifra" un șir protejat cu un cifru Cezar. Ideea este simplă: înlocuim fiecare literă în cuvânt cu o literă cu un număr fix de poziții în jos de alfabet. Voi trece 1 pentru simplitate:

Definiți MyModule pentru def << char <- cipher >>, do: char - 1 sfârșitul final MyModule.decipher ("fmjyjs") |> IO.inspect # => 'elixir'

Acest aspect seamănă exact cu exemplul precedent, cu excepția cazului << și >> părți. Luăm un cod al fiecărui caracter într-un șir, decrementăm-l cu unul și construim un șir înapoi. Deci mesajul cifrat a fost "elixirul"!

Dar totuși, există mai mult decât atât. O altă caracteristică utilă a înțelegerilor este capacitatea de a filtra anumite elemente.

Înțelegeri și filtrare

Să extindem și mai mult exemplul nostru inițial. Voi trece o serie de numere întregi 1 la 20, ia doar elementele care sunt uniforme și le multiplică 2:

defmodule MyModule nu necesită Integer def do_something (colectare) do colectare |> Stream.filter (& Integer.is_even / 1) |> Enum.map (& (* 1 2)) sfârșitul sfârșitului MyModule.do_something ((1 ... 20)) | > IO.inspect

Aici trebuia să solicit Întreg pentru a putea utiliza modulul is_even / 1 macro. De asemenea, eu folosesc Curent pentru a optimiza codul un pic și a împiedica repetarea să fie efectuată de două ori.

Acum, să redenumiți din nou acest exemplu cu o înțelegere:

 def do_something (colectare) face pentru el <- collection, Integer.is_even(el), do: el * 2 end

Deci, după cum vedeți, pentru poate accepta un filtru opțional pentru a sări peste anumite elemente din colecție.

Nu vă limitați la un singur filtru, deci și codul următor este legit:

 def do_something (colectare) face pentru el <- collection, Integer.is_even(el), el < 10, do: el * 2 end

Va lua toate numerele mai mici decât 10. Nu uitați să delimitați filtrele cu virgule.

Filtrele vor fi evaluate pentru fiecare element al colecției și dacă evaluarea va reveni Adevărat, blocul este executat. În caz contrar, este luat un nou element. Ceea ce este interesant este faptul că generatoarele pot fi, de asemenea, utilizate pentru filtrarea elementelor prin utilizarea lor cand:

 def do_something (colectare) face pentru el atunci când el < 10 <- collection, Integer.is_even(el), do: el * 2 end

Acest lucru este foarte similar cu ceea ce facem atunci când scriem clauze de pază:

def do_something (x) când este_number (x) nu # ... sfârșit

Înțelegeri cu mai multe colecții

Acum, să presupunem că nu avem una, ci două colecții simultan și am dori să producem o nouă colecție. De exemplu, luați toate numerele paralele din prima colecție și impare de la cel de-al doilea, apoi multiplicați-le:

defmodule MyModule nu necesită Integer def do_something (collection1, collection2) pentru el1 <- collection1, el2 <- collection2, Integer.is_even(el1), Integer.is_odd(el2), do: el1 * el2 end end MyModule.do_something( (1… 20), (5… 10) ) |> IO.inspect

Acest exemplu ilustrează faptul că înțelegerile pot funcționa simultan cu mai multe colecții. Primul număr par de la collection1 va fi luată și înmulțită cu fiecare număr impar din collection2. Apoi, al doilea întreg întreg de la collection1 vor fi luate și înmulțite și așa mai departe. Rezultatul va fi: 

[10,14,18,20,28,36,30,42,54,40,56,72,50,70,90,60,84,108,70,98,126,80,112,144,90 , 126, 162, 100, 140, 180]

Mai mult, valorile rezultate nu trebuie să fie întregi. De exemplu, puteți returna o trupă care conține numere întregi din prima și cea de-a doua colecție:

defmodule MyModule nu necesită Integer def do_something (collection1, collection2) pentru el1 <- collection1, el2 <- collection2, Integer.is_even(el1), Integer.is_odd(el2), do: el1,el2 end end MyModule.do_something( (1… 20), (5… 10) ) |> IO.inspect # => [2, 5, 2, 7, 2, 9, 4, 5 ...]

Cunoștințe Cu opțiunea "Into"

Până în acest moment, rezultatul final al înțelegerii noastre a fost întotdeauna o listă. De fapt, aceasta nu este obligatorie. Puteți specifica o în parametru care acceptă o colecție pentru a conține valoarea rezultată. 

Acest parametru acceptă orice structură care implementează protocolul Collectable, de exemplu, putem genera o hartă similară:

defmodule MyModule nu necesită Integer def do_something (collection1, collection2) pentru el1 <- collection1, el2 <- collection2, Integer.is_even(el1), Integer.is_odd(el2), into: Map.new, do: el1,el2 end end MyModule.do_something( (1… 20), (5… 10) ) |> IO.inspect # =>% 2 => 9, 4 => 9, 6 => 9 ...

Aici am spus pur și simplu în: Map.new, care pot fi înlocuite și cu în:% . Prin returnarea el1, el2 tuple, am stabilit în principiu primul element ca o cheie și cel de-al doilea ca valoare.

Acest exemplu nu este deosebit de util, totuși, să generăm o hartă cu un număr ca cheie și pătratul său ca valoare:

defmodule MyModule do def do_something (colectare) face pentru el <- collection, into: Map.new, do: el, :math.sqrt(el) end end squares = MyModule.do_something( (1… 20) ) |> IO.inspect # =>% 1 => 1.0, 2 => 1.4142135623730951, 3 => 1.7320508075688772, ... pătrate [3] |> IO.puts # => 1.7320508075688772

În acest exemplu folosesc Erlang : matematica modul în care, în definitiv, numele tuturor modulelor sunt atomi. Acum puteți găsi ușor pătratul pentru orice număr de la 1 la 20.

Înțelegeri și potrivirea modelelor

Ultimul lucru pe care trebuie să-l menționăm este că puteți efectua potrivirea modelului și în înțelegeri. În unele cazuri, poate fi destul de la îndemână.

Să presupunem că avem o hartă care conține numele angajaților și salariile lor brute:

% "Joe" => 50, "Bill" => 40, "Alice" => 45, "Jim" => 30

Vreau să generez o nouă hartă unde numele sunt reduse și convertite în atomi, iar salariile sunt calculate folosind o rată de impozitare:

defmodule MyModule nu @tax 0.13 def format_employee_data (colectare) face pentru name, salary <- collection, into: Map.new, do: format_name(name), salary - salary * @tax end defp format_name(name) do name |> String.downcase |> String.to_atom sfârșitul sfârșitului MyModule.format_employee_data (% "Joe" => 50, "Bill" => 40, "Alice" => 45, "Jim" => 30)> IO.inspect # =>% alice: 39,15, factură: 34,8, jim: 26,1, joe: 43,5

În acest exemplu definim un atribut al unui modul @impozit cu un număr arbitrar. Apoi dezconstruiesc datele în înțelegere folosind name, salary <- collection. În cele din urmă, formatați numele și calculați salariul după cum este necesar și stocați rezultatul în noua hartă. Destul de simplu, dar expresiv.

Concluzie

În acest articol am văzut cum să folosim înțelegerea Elixir. Veți avea nevoie de ceva timp pentru a vă obișnuiți. Acest construct este foarte elegant și în unele situații se poate potrivi mult mai bine decât funcții precum Hartă și filtru. Puteți găsi mai multe exemple în documentele oficiale ale Elixir și în ghidul de inițiere.

Sperăm că ați găsit acest tutorial util și interesant! Vă mulțumim că ați rămas cu mine și vă voi vedea în curând.

Cod