Iteraptor: bibliothĂšque pour redus de carte transparent profond

Les structures de données Elixir sont immuables. C'est génial du point de vue de la confiance que nos données ne seront pas corrompues au-delà de la reconnaissance dans un autre morceau de code non pertinent, mais c'est un peu ennuyeux lorsque nous devons changer une structure profondément imbriquée.


Nous avons une brillante abstraction Access , qui simplifie considérablement quatre opérations de base sur des objets profondément imbriqués en utilisant les fonctions exportées par défaut du Kernel :



Ces quatre mousquetaires (et d'Artagnan Kernel.get_and_update_in/{2,3} sont généralement utilisés quelque chose comme ceci:


 iex> users = %{"john" => %{age: 27, mood: ""}, "meg" => %{age: 23}} #   iex> get_in(users, ["john", :age]) #⇒ 27 #   iex> put_in(users, ["john", :age], 28) #⇒ %{"john" => %{age: 28, mood: ""}, "meg" => %{age: 23}} #   iex> update_in(users, ["john", :mood], & &1 + 1) #⇒ %{"john" => %{age: 28, mood: ""}, "meg" => %{age: 23}} #   iex> pop_in(users, ["john", :mood]) #⇒ {"", %{"john" => %{age: 27}, "meg" => %{age: 23}}} 

Il est pratique et fonctionne dans de nombreux cas ... sauf pour ceux oĂč cela ne fonctionne pas. Pour utiliser Access , vous devez connaĂźtre le chemin d'accĂšs Ă  l'Ă©lĂ©ment cible, et tout cela nĂ©cessite une quantitĂ© importante de code passe-partout afin de mettre Ă  jour plusieurs valeurs imbriquĂ©es Ă  la fois (par exemple, supprimez toutes les feuilles avec la valeur nil ou mettez en Ă©toile le contenu de tous les champs qui n'ont pas besoin d'ĂȘtre affichĂ©s dans les journaux).


Pour offrir des remises en gros sur l'utilisation de structures imbriquées, la bibliothÚque Iteraptor a été créée.


TL; DR:





ItĂ©rer tout ce que vous pouvez rĂ©pĂ©ter comme par magie Ă  Elixir . Pour le rendre itĂ©rable, implĂ©mentez simplement le protocole Enumerable pour ce type particulier. Vous pouvez regrouper les passages en pipelines, cartographier, rĂ©duire, filtrer, Ă©claircir ... Pardonnez mon français, oui. Tous ceux qui ont passĂ© au moins huit heures avec Elixir ont certainement vu (et peut-ĂȘtre mĂȘme Ă©crit) quelque chose comme ceci:


 ~w|   | |> Enum.map(&String.capitalize/1) |> Enum.each(fn capitalized_name -> IO.puts "Hello, #{capitalized_name}!" end) # Hello, ! # Hello, ! # Hello, ! # Hello, ! 

C'est vraiment trÚs pratique. Cependant, le code devient rapidement lourd lorsqu'il s'agit de structures profondément imbriquées, telles qu'une carte avec des mots - clés imbriqués, des listes, etc. Un bon exemple de ceci est tout fichier de configuration contenant des sous-sections imbriquées.


Le nombre de questions sur Stack Overflow avec la question «comment puis-je changer la structure imbriquĂ©e?» M'a forcĂ© Ă  enfin crĂ©er cette bibliothĂšque. La mise en Ɠuvre chez Elixir semble un peu dĂ©routante, car tout autour est immuable, et vous ne pouvez pas simplement descendre les branches de la structure jusqu'aux feuilles, en changeant tout ce dont vous avez besoin en place. Vous aurez besoin d'une batterie, comme, cependant, sous n'importe quel capot d'un code fonctionnel. Changer les structures imbriquĂ©es est probablement le seul exemple que j'ai vu dans ma vie oĂč la mutabilitĂ© facilite les choses.


En plus des prĂ©curseurs de carte habituels, j'ai ajoutĂ© une implĂ©mentation pour stocker la valeur au plus profond de la structure, ce qui crĂ©e des clĂ©s intermĂ©diaires selon les besoins. Il se comporte comme proposĂ©, mais rejetĂ© dans le noyau rubis Hash#bury . Cette bibliothĂšque sait Ă©galement comment "jasoniser" des structures imbriquĂ©es contenant des mots clĂ©s qui ne peuvent pas ĂȘtre simplement sĂ©rialisĂ©s en json , car Ă  l'intĂ©rieur, ils sont prĂ©sentĂ©s comme des listes de tuples Ă  deux Ă©lĂ©ments ( [foo: :bar] == [{:foo, :bar}] ), et les tuples ne sont pas sĂ©rialisables hors de la boĂźte.




Alors, saluons la bibliothÚque, qui itÚre sur n'importe quelle carte / mot - clé / liste dans la queue et la criniÚre est presque aussi simple que les Enum.each/2 et Enum.map/2 .


Les possibilités


  • Iteraptor.each/3 simple itĂ©ration, retourne la structure elle-mĂȘme;
  • Iteraptor.map/3 mappage Iteraptor.map/3 , renvoie une structure mappĂ©e;
  • Iteraptor.reduce/4 reduus, renvoie la batterie;
  • Iteraptor.map_reduce/4 map et redus, renvoie un tuple avec le rĂ©sultat de la cartographie et de la batterie;
  • Iteraptor.filter/3 filtre la structure en utilisant la fonction obtenue par le dernier paramĂštre;
  • Iteraptor.jsonify/2 prĂ©pare la structure pour la sĂ©rialisation en json : tous les mots clĂ©s sont remplacĂ©s par des cartes , les clĂ©s sont converties en chaĂźnes;
  • Iteraptor.Extras.bury/4 Ă©crit la valeur profondĂ©ment dans la structure, crĂ©ant des clĂ©s intermĂ©diaires selon les besoins;
  • Iteraptor.to_flatmap/2 transforme la structure en une structure plate, crĂ©ant soigneusement des index pour les listes; la structure d'origine peut ĂȘtre rĂ©cupĂ©rĂ©e en utilisant
  • Iteraptor.from_flatmap/3 transforme une structure platunk avec des clĂ©s concatĂ©nĂ©es en une structure profondĂ©ment imbriquĂ©e;
  • use Iteraptor.Iteraptable implĂ©mentation automatique des protocoles Enumerable et Collectable , ainsi que le comportement Access pour la structure. Il y a une description plus dĂ©taillĂ©e de ce qui est sous le capot.

Les mots ne coûtent rien, montrez le code!


Itération, cartographie, réduction


 # each iex> %{a: %{b: %{c: 42}}} |> Iteraptor.each(&IO.inspect(&1, label: "each"), yield: :all) # each: {[:a], %{b: %{c: 42}}} # each: {[:a, :b], %{c: 42}} # each: {[:a, :b, :c], 42} %{a: %{b: %{c: 42}}} # map iex> %{a: %{b: %{c: 42}}} |> Iteraptor.map(fn {k, _} -> Enum.join(k) end) %{a: %{b: %{c: "abc"}}} iex> %{a: %{b: %{c: 42}, d: "some"}} ...> |> Iteraptor.map(fn ...> {[_], _} = self -> self ...> {[_, _], _} -> "********" ...> end, yield: :all) %{a: %{b: "********", d: "some"}} # reduce iex> %{a: %{b: %{c: 42}}} ...> |> Iteraptor.reduce([], fn {k, _}, acc -> ...> [Enum.join(k, "_") | acc] ...> end, yield: :all) ...> |> :lists.reverse() ["a", "a_b", "a_b_c"] # map-reduce iex> %{a: %{b: %{c: 42}}} ...> |> Iteraptor.map_reduce([], fn ...> {k, %{} = v}, acc -> {​{k, v}, [Enum.join(k, ".") | acc]} ...> {k, v}, acc -> {​{k, v * 2}, [Enum.join(k, ".") <> "=" | acc]} ...> end, yield: :all) {​%{a: %{b: %{c: 42}}}, ["abc=", "ab", "a"]} # filter iex> %{a: %{b: 42, e: %{f: 3.14, c: 42}, d: %{c: 42}}, c: 42, d: 3.14} ...> |> Iteraptor.filter(fn {key, _} -> :c in key end, yield: :none) %{a: %{e: %{c: 42}, d: %{c: 42}}, c: 42} 

Imbrication profonde → structure plate et vice versa


 iex> %{a: %{b: %{c: 42, d: [nil, 42]}, e: [:f, 42]}} ...> |> Iteraptor.to_flatmap(delimiter: "_") #⇒ %{"a_b_c" => 42, "a_b_d_0" => nil, "a_b_d_1" => 42, "a_e_0" => :f, "a_e_1" => 42} iex> %{"abc": 42, "abd0": nil, "abd1": 42, "ae0": :f, "ae1": 42} ...> |> Iteraptor.from_flatmap #⇒ %{a: %{b: %{c: 42, d: [nil, 42]}, e: [:f, 42]}} 

Brioches


 iex> Iteraptor.jsonify([foo: [bar: [baz: :zoo], boo: 42]], values: true) %{"foo" => %{"bar" => %{"baz" => "zoo"}, "boo" => 42}} iex> Iteraptor.Extras.bury([foo: :bar], ~w|abcd|a, 42) [a: [b: [c: [d: 42]]], foo: :bar] 



Les sources sont ouvertes , la documentation est assez détaillée , nous sommes en production depuis prÚs de deux ans maintenant.


Bonne itération!

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


All Articles