Elixir is een erg jonge programmeertaal (ontstaan in 2011), maar het wint aan populariteit. Ik was in eerste instantie geïnteresseerd in deze taal, omdat je bij het gebruik een aantal veelvoorkomende taken kunt bekijken die programmeurs meestal vanuit een andere hoek oplossen. U kunt bijvoorbeeld leren hoe u collecties kunt herhalen zonder de voor
cyclus, of hoe u uw code zonder klassen kunt organiseren.
Elixir heeft enkele zeer interessante en krachtige functies die je misschien moeilijk kunt vinden als je uit de OOP-wereld komt. Na enige tijd begint het echter allemaal logisch te worden en zie je hoe expressief de functionele code kan zijn. Begrip is zo'n eigenschap en in dit artikel zal ik uitleggen hoe je ermee kunt werken.
In het algemeen is een lijstbegrip een speciaal concept waarmee u een nieuwe lijst kunt maken op basis van bestaande. Dit concept is te vinden in talen als Haskell en Clojure. Erlang presenteert het ook en daarom heeft Elixir ook inzichten.
Je zou je kunnen afvragen hoe de begrippen verschillen van de kaart / 2-functie, die ook een verzameling kost en een nieuwe produceert? Dat zou een eerlijke vraag zijn! Welnu, in het eenvoudigste geval doen begrippen vrijwel hetzelfde. Bekijk dit voorbeeld:
defmodule MyModule do def do_something (lijst) takenlijst |> Enum.map (fn (el) -> el * 2 end) end end MyModule.do_something ([1,2,3]) |> IO.inspect # => [ 2,4,6]
Hier neem ik gewoon een lijst met drie nummers en maak ik een nieuwe lijst met alle getallen vermenigvuldigd met 2
. De kaart
oproep kan verder worden vereenvoudigd als Enum.map (& (& 1 * 2))
.
De do_something / 1
functie kan nu worden herschreven met behulp van een begrip:
def do_something (lijst) doen voor el <- list, do: el * 2 end
Zo ziet een basisbegrip eruit en naar mijn mening is de code iets eleganter dan in het eerste voorbeeld. Hier nemen we opnieuw elk element uit de lijst en vermenigvuldigen het met 2
. De el <- list
deel heet a generator, en het legt uit hoe je precies de waarden uit je verzameling wilt halen.
Merk op dat we niet gedwongen worden om een lijst door te geven aan de do_something / 1
function-de code zal werken met alles dat opsombaar is:
defmodule MyModule do def do_something (verzameling) doen voor el <- collection, do: el * 2 end end MyModule.do_something((1… 3)) |> IO.inspect
In dit voorbeeld geef ik een bereik door als argument.
Begrip werkt ook met binstrings. De syntaxis is iets anders omdat je je generator moet omsluiten <<
en >>
. Laten we dit demonstreren door een heel eenvoudige functie te maken om een string te 'ontcijferen' die beschermd is met een Caesar-cijfer. Het idee is simpel: we vervangen elke letter in het woord door een letter een vast aantal posities in het alfabet. Ik ga langs 1
positie voor eenvoud:
defmodule MyModule do def decipher (cipher) do for << char <- cipher >>, do: char - 1 end end MyModule.decipher ("fmjyjs") |> IO.inspect # => 'elixir'
Dit ziet er ongeveer hetzelfde uit als in het vorige voorbeeld behalve de <<
en >>
onderdelen. We nemen een code van elk teken in een string, verlagen deze met één en bouwen een string terug. Dus de versleutelde boodschap was "elixir"!
Maar toch, er is meer dan dat. Een ander handig kenmerk van begrippen is het vermogen om bepaalde elementen eruit te filteren.
Laten we ons eerste voorbeeld verder uitbreiden. Ik ga een reeks van gehele getallen doorgeven 1
naar 20
, neem alleen de elementen die gelijk zijn en vermenigvuldig deze met 2
:
defmodule MyModule vereist Integer def do_something (verzameling) do collection |> Stream.filter (& Integer.is_even / 1) |> Enum.map (& (& 1 * 2)) end end MyModule.do_something ((1 ... 20)) | > IO.inzicht
Hier moest ik het Geheel getal
module om de. te kunnen gebruiken is_even / 1
macro. Ook gebruik ik Stroom
om de code een beetje te optimaliseren en te voorkomen dat de iteratie tweemaal wordt uitgevoerd.
Laten we dit voorbeeld nu opnieuw herschrijven met een begrip:
def do_something (verzameling) doen voor el <- collection, Integer.is_even(el), do: el * 2 end
Dus, zoals je ziet, voor
kan een optionele filter accepteren om sommige elementen uit de verzameling over te slaan.
U bent niet beperkt tot slechts één filter, dus de volgende code is ook legitiem:
def do_something (verzameling) doen voor el <- collection, Integer.is_even(el), el < 10, do: el * 2 end
Het zal alle even aantallen minder dan nodig hebben 10
. Vergeet niet om filters met komma's af te bakenen.
De filters worden geëvalueerd voor elk element van de verzameling en als de evaluatie terugkeert waar
, het blok wordt uitgevoerd. Anders wordt een nieuw element gebruikt. Wat interessant is, is dat generatoren ook kunnen worden gebruikt om elementen uit te filteren met behulp van wanneer
:
def do_something (verzameling) doen voor el wanneer el < 10 <- collection, Integer.is_even(el), do: el * 2 end
Dit is erg vergelijkbaar met wat we doen bij het schrijven van bewaringsclausules:
def do_something (x) wanneer is_number (x) do # ... eindigen
Stel nu dat we niet één maar twee collecties tegelijk hebben en we willen graag een nieuwe collectie produceren. Neem bijvoorbeeld alle even nummers uit de eerste verzameling en oneven uit de tweede en vermenigvuldig ze vervolgens:
defmodule MyModule vereist Integer def do_something (collection1, collection2) do 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
Dit voorbeeld illustreert dat begrippen in één keer met meerdere verzamelingen werken. Het eerste even getal van collection1
zal worden genomen en vermenigvuldigd met elk oneven getal uit verzamelen2
. Vervolgens het tweede even gehele getal van collection1
zal worden genomen en vermenigvuldigd, enzovoort. Het resultaat zal zijn:
[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]
Bovendien hoeven de resulterende waarden geen gehele getallen te zijn. U kunt bijvoorbeeld een tuple met gehele getallen retourneren uit de eerste en de tweede verzameling:
defmodule MyModule vereist Integer def do_something (collection1, collection2) do 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 ...]
Tot nu toe was het uiteindelijke resultaat van ons begrip altijd een lijst. Dit is eigenlijk ook niet verplicht. U kunt een opgeven in
parameter die een verzameling accepteert om de resulterende waarde te bevatten.
Deze parameter accepteert elke structuur die het verzamelprotocol implementeert, dus we kunnen bijvoorbeeld een kaart als deze genereren:
defmodule MyModule vereist Integer def do_something (collection1, collection2) do 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 ...
Hier zei ik eenvoudigweg naar: Map.new
, welke ook kan worden vervangen door in:%
. Door de el1, el2
tuple, stellen we in feite het eerste element in als een sleutel en het tweede als de waarde.
Dit voorbeeld is echter niet erg handig, dus laten we een kaart met een nummer als een sleutel genereren en het vierkant als een waarde:
defmodule MyModule do def do_something (verzameling) doen voor 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, ... vierkanten [3] |> IO.puts # => 1.7320508075688772
In dit voorbeeld gebruik ik Erlang's :wiskunde
module direct, omdat alle namen van modules immers atomen zijn. Nu kunt u eenvoudig het vierkant voor elk nummer vinden 1
naar 20
.
Het laatste ding om op te noemen is dat je ook patroonvergelijking in begrippen kunt uitvoeren. In sommige gevallen kan het handig zijn.
Stel dat we een kaart hebben met de namen van werknemers en hun onbewerkte salarissen:
% "Joe" => 50, "Factuur" => 40, "Alice" => 45, "Jim" => 30
Ik wil een nieuwe kaart genereren waarin de namen worden teruggebracht en omgezet in atomen, en salarissen worden berekend aan de hand van een belastingtarief:
defmodule MyModule do @tax 0.13 def format_employee_data (verzameling) do voor naam, salaris <- collection, into: Map.new, do: format_name(name), salary - salary * @tax end defp format_name(name) do name |> String.downcase |> String.to_atom end end MyModule.format_employee_data (% "Joe" => 50, "Bill" => 40, "Alice" => 45, "Jim" => 30) |> IO.inspect # =>% alice: 39.15, bill: 34.8, jim: 26.1, joe: 43.5
In dit voorbeeld definiëren we een modulekenmerk @belasting
met een willekeurig aantal. Vervolgens deconstrueer ik de gegevens in het begrip met behulp van naam, salaris <- collection
. Formuleer tenslotte de naam en bereken zo nodig het salaris en sla het resultaat op de nieuwe kaart op. Heel eenvoudig maar toch expressief.
In dit artikel hebben we gezien hoe Elixir-begrippen te gebruiken. Je hebt misschien wat tijd nodig om eraan te wennen. Deze constructie is echt netjes en past in sommige situaties veel beter dan functies als kaart
en filter
. U kunt nog enkele voorbeelden vinden in de officiële documenten van Elixir en de beknopte handleiding.
Hopelijk heb je deze tutorial nuttig en interessant gevonden! Bedankt dat je bij me bent gebleven en tot snel.