Angaben zum Dialysator: Der Weg der Jedi

Es gibt zwei Arten von Entwicklern, die erlang und elixir verwenden: diejenigen, die Spezifikationen für Dialyzer schreiben, und diejenigen, die es noch nicht sind. Auf den ersten Blick scheint alles Zeitverschwendung zu sein, besonders für diejenigen, die aus Sprachen mit losen Schreibweisen stammen. Sie haben mir jedoch geholfen, mehr als einen Fehler vor der CI-Phase zu finden, und früher oder später hat jeder Entwickler verstanden, dass sie benötigt werden. Dies ist nicht nur ein Hilfsmittel für die semistrikte Eingabe, sondern auch eine große Hilfe bei der Dokumentation von Code.


Aber wie es in unserer grausamen Welt immer der Fall ist, kann man in jedem guten Fass nicht auf einen Löffel verzichten. Im Wesentlichen duplizieren die @spec Direktiven den Funktionsdeklarationscode. Im Folgenden erkläre ich Ihnen, wie zwanzig Codezeilen helfen, die Spezifikation und Deklaration einer Funktion in einem Entwurf zu kombinieren


 defs is_forty_two(n: integer) :: boolean do n == 42 end 

Wie Sie wissen, enthält das Elixier nur Makros. Auch Kernel.defmacro/2 ist ein . Daher müssen wir nur unser eigenes Makro definieren, das aus der obigen Konstruktion sowohl die Spezifikations- als auch die Funktionsdeklaration erstellt.


Nun, lass uns anfangen.


Schritt 1. Studieren Sie die Situation.


Zunächst werden wir verstehen, welche Art von AST unser Makro als Argumente erhält.


 defmodule CustomSpec do defmacro defs(args, do: block) do IO.inspect(args) :ok end end defmodule CustomSpec.Test do import CustomSpec defs is_forty_two(n: integer) :: boolean do n == 42 end end 

Hier rebelliert der Formatierer, fügt Klammern hinzu und formatiert den Code darin so, dass Tränen aus den Augen fließen. Entwöhne ihn davon. Ändern Sie die Konfigurationsdatei .formatter.exs wie .formatter.exs :


 [ inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"], export: [locals_without_parens: [defs: 2]] ] 

Gehen wir zurück zu unseren Schafen und sehen, was defs/2 bekommt. Es ist zu beachten, dass unser IO.inspect/2 in der Kompilierungsphase funktioniert (wenn Sie nicht verstehen, warum Sie noch nicht mit Makros spielen müssen, lesen Sie das brillante Metaprogramming Elixir- Buch von Chris McCord). Damit der Compiler nicht schwört, geben wir zurück :ok (Makros müssen den korrekten AST zurückgeben). Also:


 {:"::", [line: 7], [ {:is_forty_two, [line: 7], [[n: {:integer, [line: 7], nil}]]}, {:boolean, [line: 7], nil} ]} 

Ja Der Parser ist der Ansicht, dass das Wichtigste hier der Operator :: , der die Funktionsdefinition und den Rückgabetyp zusammenfügt. Die Funktionsdefinition enthält auch eine Liste von Parametern in Form des Keyword „Parametername → Typ“.


Schritt 2. Schnell scheitern.


Da wir uns bisher entschieden haben, nur eine solche Aufrufsyntax zu unterstützen, müssen wir die Definition des defs Makros neu schreiben, damit der Compiler sofort schwört, wenn beispielsweise der Rückgabetyp nicht angegeben ist.


 defmacro defs({:"::", _, [{fun, _, [args_spec]}, {ret_spec, _, nil}]}, do: block) do 

Nun, es ist Zeit, mit der Implementierung zu beginnen.


Schritt 3. Generieren Sie Spezifikationen und Funktionsdeklarationen.


 defmodule CustomSpec do defmacro defs({:"::", _, [{fun, _, [args_spec]}, {ret_spec, _, nil}]}, do: block) do #     args = for {arg, _spec} <- args_spec, do: Macro.var(arg, nil) #    args_spec = for {_arg, spec} <- args_spec, do: Macro.var(spec, nil) quote do @spec unquote(fun)(unquote_splicing(args_spec)) :: unquote(ret_spec) def unquote(fun)(unquote_splicing(args)) do unquote(block) end end end end 

Hier ist alles so transparent, dass es nichts zu kommentieren gibt.


Es ist Zeit zu sehen, CustomSpec.Test.is_forty_two(42) der Aufruf von CustomSpec.Test.is_forty_two(42) führen wird:


 iex> CustomSpec.Test.is_forty_two 42 #⇒ true iex> CustomSpec.Test.is_forty_two 43 #⇒ false 

Nun, es funktioniert.


Schritt 4. Ist das alles?


Natürlich nicht. Im wirklichen Leben müssen Sie ungültige Aufrufe, Header-Definitionen für Funktionen mit verschiedenen Standardparametern korrekt verarbeiten, die Spezifikation mit Variablennamen sorgfältig erfassen, sicherstellen, dass alle Argumentnamen unterschiedlich sind, und vieles mehr. Aber als Leistungsnachweis genügt.


Grundsätzlich können Sie Kollegen immer noch mit so etwas überraschen:


 defmodule CustomSpec do defmacro __using__(_) do import Kernel, except: [def: 2] import CustomSpec defmacro def(args, do: block) do defs(args, do: block) end end ... end 

(Es muss immer noch defs/2 korrigiert werden, wobei Kernel.def anstelle von def Kernel.def wird), aber ich würde dringend davon Kernel.def , dies zu tun.


Vielen Dank für Ihre Aufmerksamkeit, Makro Gesundheit!

Source: https://habr.com/ru/post/de480466/


All Articles