Wie man mit Elixir-Verständnis arbeitet

Elixir ist eine sehr junge Programmiersprache (wurde 2011 entwickelt), erfreut sich jedoch immer größerer Beliebtheit. Anfangs war ich an dieser Sprache interessiert, denn wenn Sie sie verwenden, können Sie einige allgemeine Aufgaben betrachten, die Programmierer normalerweise aus einem anderen Blickwinkel lösen. So können Sie beispielsweise herausfinden, wie Sammlungen ohne das Durchlaufen von Sammlungen durchlaufen werden zum Zyklus oder wie Sie Ihren Code ohne Klassen organisieren.

Elixir verfügt über einige sehr interessante und leistungsstarke Funktionen, die möglicherweise schwer zu verstehen sind, wenn Sie aus der OOP-Welt kommen. Nach einiger Zeit wird jedoch alles sinnvoll und Sie sehen, wie ausdrucksstark der Funktionscode sein kann. Verständnis ist ein solches Merkmal, und in diesem Artikel werde ich erklären, wie man mit ihnen arbeitet.

Verständnis und Mapping

Im Allgemeinen ist ein Listenverständnis ein spezielles Konstrukt, mit dem Sie eine neue Liste basierend auf vorhandenen Listen erstellen können. Dieses Konzept findet sich in Sprachen wie Haskell und Clojure. Erlang stellt es auch vor und daher hat Elixir auch Verständnis.

Sie fragen sich vielleicht, wie sich das Verständnis von der Map / 2-Funktion unterscheidet, die auch eine Sammlung benötigt und eine neue produziert. Das wäre eine faire Frage! Nun, im einfachsten Fall machen Verstehen ziemlich dasselbe. Schauen Sie sich dieses Beispiel an:

defmodule MyModule do def do_something (liste) do list |> Enum.map (fn (el) -> el * 2 end) end end MyModule.do_something ([1,2,3]) |> IO.inspect # => [ 2,4,6]

Hier nehme ich einfach eine Liste mit drei Zahlen und erstelle eine neue Liste mit allen Zahlen, die mit multipliziert werden 2. Das Karte Anruf kann weiter vereinfacht werden als Enum.map (& (& 1 * 2)).

Das do_something / 1 Funktion kann jetzt mit einem Verständnis umgeschrieben werden:

 def do_something (list) für el tun <- list, do: el * 2 end

So sieht ein grundlegendes Verständnis aus und meiner Meinung nach ist der Code etwas eleganter als im ersten Beispiel. Auch hier nehmen wir jedes Element aus der Liste und multiplizieren es mit 2. Das el <- list Teil heißt a Generator, und es erklärt, wie genau Sie die Werte aus Ihrer Sammlung extrahieren möchten.

Beachten Sie, dass wir nicht gezwungen sind, eine Liste an das System zu übergeben do_something / 1 Funktion - der Code funktioniert mit allem, was aufzulesen ist:

defmodule MyModule tut def do_something (collection) für el <- collection, do: el * 2 end end MyModule.do_something((1… 3)) |> IO.inspect

In diesem Beispiel übergebe ich einen Bereich als Argument.

Erkenntnisse funktionieren auch mit Binstrings. Die Syntax ist etwas anders, da Sie Ihren Generator mit einschließen müssen << und >>. Lassen Sie uns dies demonstrieren, indem Sie eine sehr einfache Funktion erstellen, um eine mit einer Caesar-Verschlüsselung geschützte Zeichenfolge zu "entschlüsseln". Die Idee ist einfach: Wir ersetzen jeden Buchstaben im Wort durch einen Buchstaben und eine feste Anzahl von Positionen im Alphabet. Ich schiebe vorbei 1 Position zur Vereinfachung:

defmodule MyModule tut für die Entschlüsselung (Verschlüsselung) << char <- cipher >>, do: char - 1 Ende Ende MyModule.decipher ("fmjyjs") |> IO.inspect # => 'elixir'

Dies sieht ziemlich ähnlich aus wie das vorige Beispiel mit Ausnahme des << und >> Teile. Wir nehmen einen Code von jedem Zeichen in einer Zeichenfolge, dekrementieren es um eins und konstruieren eine Zeichenfolge zurück. Die verschlüsselte Nachricht war also "Elixier"!

Aber es gibt noch mehr als das. Eine weitere nützliche Funktion von Verständnis ist die Möglichkeit, einige Elemente herauszufiltern.

Verständnis und Filterung

Lassen Sie uns unser erstes Beispiel noch erweitern. Ich werde eine Reihe von ganzen Zahlen aus 1 zu 20, Nimm nur die Elemente, die gerade sind, und multipliziere sie mit 2:

defmodule MyModule erfordert Integer def do_something (collection) do collection | Stream.filter (& Integer.is_even / 1) Enum.map (& (1) 2) end end MyModule.do_something ((1… 20)) > IO.inspect

Hier musste ich das verlangen Ganze Zahl Modul verwenden zu können is_even / 1 Makro. Ich benutze auch Strom um den Code etwas zu optimieren und zu verhindern, dass die Iteration zweimal ausgeführt wird.

Lassen Sie uns dieses Beispiel mit einem neuen Verständnis erneut schreiben:

 def do_something (collection) für el tun <- collection, Integer.is_even(el), do: el * 2 end

Wie du siehst, zum kann einen optionalen Filter akzeptieren, um einige Elemente aus der Auflistung zu überspringen.

Sie sind nicht nur auf einen Filter beschränkt, daher ist der folgende Code auch legitim:

 def do_something (collection) für el tun <- collection, Integer.is_even(el), el < 10, do: el * 2 end

Es werden alle geraden Zahlen weniger als sein 10. Vergessen Sie nicht, Filter durch Kommas zu begrenzen.

Die Filter werden für jedes Element der Sammlung ausgewertet wahr, Der Block wird ausgeführt. Andernfalls wird ein neues Element übernommen. Interessant ist, dass Generatoren auch verwendet werden können, um Elemente mit herauszufiltern wann:

 def do_something (collection) für el tun, wenn el < 10 <- collection, Integer.is_even(el), do: el * 2 end

Dies ist sehr ähnlich dem, was wir beim Schreiben von Schutzklauseln machen:

def do_something (x), wenn is_number (x) #… beendet ist

Verständnis mit mehreren Sammlungen

Nehmen wir an, wir haben nicht nur eine, sondern zwei Kollektionen gleichzeitig, und wir möchten eine neue Sammlung erstellen. Nehmen Sie zum Beispiel alle geraden Zahlen aus der ersten Sammlung und ungerade aus der zweiten und multiplizieren Sie diese:

defmodule MyModule erfordert Integer def do_something (collection1, collection2) für 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

Dieses Beispiel zeigt, dass Verständnis für mehrere Sammlungen gleichzeitig verwendet werden kann. Die erste gerade Nummer von collection1 wird genommen und mit jeder ungeraden Zahl von multipliziert collection2. Als nächstes die zweite gerade ganze Zahl aus collection1 wird genommen und multipliziert und so weiter. Das Ergebnis wird sein: 

[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]

Darüber hinaus müssen die resultierenden Werte keine Ganzzahlen sein. Sie können beispielsweise ein Tupel mit Ganzzahlen aus der ersten und der zweiten Auflistung zurückgeben:

defmodule MyModule erfordert Integer def do_something (collection1, collection2) für 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…]

Verständnis mit der "Into" Option

Bis zu diesem Punkt war das Endergebnis unseres Verständnisses immer eine Liste. Dies ist eigentlich auch nicht zwingend. Sie können ein angeben in Parameter, der eine Sammlung akzeptiert, um den resultierenden Wert zu enthalten. 

Dieser Parameter akzeptiert jede Struktur, die das Collectable-Protokoll implementiert. Daher können wir beispielsweise eine Karte wie folgt generieren:

defmodule MyModule erfordert Integer def do_something (collection1, collection2) für 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 habe ich einfach gesagt in: Map.new, die auch durch ersetzt werden kann in:% . Durch die Rückkehr der el1, el2 Tupel setzen wir grundsätzlich das erste Element als Schlüssel und das zweite als Wert.

Dieses Beispiel ist jedoch nicht besonders nützlich. Generieren Sie also eine Map mit einer Zahl als Schlüssel und einem Quadrat als Wert:

defmodule MyModule tut def do_something (collection) für 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,… Quadrate [3] |> IO.puts # => 1.7320508075688772

In diesem Beispiel verwende ich Erlang's :Mathematik Modul direkt, da schließlich alle Modulnamen Atome sind. Nun kann man das Quadrat für jede beliebige Nummer leicht finden 1 zu 20.

Verständnis und Musterabgleich

Das letzte, was zu erwähnen ist, ist, dass Sie Pattern-Matching auch in Verständniss durchführen können. In einigen Fällen kann es sehr nützlich sein.

Angenommen, wir haben eine Karte mit den Namen der Mitarbeiter und ihren Rohgehältern:

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

Ich möchte eine neue Karte erstellen, in der die Namen heruntergerechnet und in Atome umgewandelt und die Gehälter mit einem Steuersatz berechnet werden:

defmodule MyModule do @tax 0.13 def format_employee_data (Sammlung) do für Name, Gehalt <- 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, rechnung: 34.8, jim: 26.1, joe: 43.5

In diesem Beispiel definieren wir ein Modulattribut @MwSt mit einer beliebigen Nummer. Dann dekonstruiere ich die Daten im Verständnis mit Name, Gehalt <- collection. Formatieren Sie schließlich den Namen, berechnen Sie das Gehalt nach Bedarf und speichern Sie das Ergebnis in der neuen Karte. Ganz einfach, aber ausdrucksstark.

Fazit

In diesem Artikel haben wir gesehen, wie Sie Elixir-Verständnis verwenden können. Möglicherweise benötigen Sie etwas Zeit, um sich an sie zu gewöhnen. Dieses Konstrukt ist wirklich ordentlich und kann in manchen Situationen besser passen als Funktionen Karte und Filter. Weitere Beispiele finden Sie in den offiziellen Dokumenten von Elixir und in der Kurzanleitung.

Hoffentlich fanden Sie dieses Tutorial nützlich und interessant! Vielen Dank, dass Sie bei mir geblieben sind, und bis bald.