Iteraptor: biblioteca para redus transparente profundo do mapa

As estruturas de dados do Elixir são imutáveis. Isso é ótimo do ponto de vista da crença de que nossos dados não serão corrompidos além do reconhecimento em algum outro pedaço de código irrelevante, mas é um pouco irritante quando precisamos alterar uma estrutura profundamente aninhada.


Temos uma brilhante abstração do Access , que simplifica bastante quatro operações básicas em objetos profundamente aninhados usando as funções padrão exportadas do Kernel :



Esses quatro mosqueteiros (e o d'Artagnan Kernel.get_and_update_in/{2,3} são geralmente usados ​​da seguinte maneira:


 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}}} 

Isso é conveniente e funciona em muitos casos ... exceto nos casos em que não funciona. Para usar o Access , você precisa conhecer o caminho para o elemento de destino, e tudo isso requer uma quantidade significativa de código padrão para atualizar vários valores aninhados de uma só vez (por exemplo, exclua todas as folhas com o valor nil ou nil estrela o conteúdo de todos os campos que não precisam ser mostrados nos logs).


Para fornecer descontos por atacado no trabalho com estruturas aninhadas, a biblioteca Iteraptor foi criada.


TL; DR:





Iterando tudo o que você pode iterar magicamente no Elixir . Para torná-lo iterável, basta implementar o protocolo Enumerable para esse tipo específico. Você pode agrupar passagens em dutos, mapear, reduzir, filtrar, afinar ... Perdoe meu francês, sim. Todo mundo que passou pelo menos oito horas com Elixir definitivamente viu (e possivelmente escreveu) algo assim:


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

É realmente muito conveniente. No entanto, o código rapidamente se torna complicado quando se trata de estruturas profundamente aninhadas, como um mapa com palavras-chave , listas, etc. Um bom exemplo disso é qualquer arquivo de configuração que contém subseções aninhadas.


O número de perguntas no Stack Overflow com a pergunta "como posso alterar a estrutura aninhada?" Forçou-me a finalmente criar esta biblioteca. A implementação no Elixir parece um pouco confusa, porque tudo ao redor é imutável e você não pode simplesmente descer os galhos da estrutura até as folhas, mudando tudo o que você precisa no local. Você precisará de uma bateria, como, no entanto, sob qualquer capa de um código funcional. Alterar estruturas aninhadas é provavelmente o único exemplo que já vi na minha vida quando a mutabilidade facilita as coisas.


Como um bônus aos precursores regulares de mapas, adicionei uma implementação para armazenar o valor dentro da estrutura, que cria chaves intermediárias, conforme necessário. Ele se comporta como proposto, mas rejeitado no núcleo de rubi Hash#bury . Essa biblioteca também sabe como "jasonizar" estruturas aninhadas que contêm palavras-chave que não podem ser simplesmente serializadas no json , porque por dentro são apresentadas como listas de tuplas de dois elementos ( [foo: :bar] == [{:foo, :bar}] , e tuplas não podem ser serializadas prontamente.




Então, vamos dar as boas-vindas à biblioteca, que itera sobre qualquer mapa / palavra-chave / lista na cauda e na juba quase tão facilmente quanto o padrão Enum.each/2 e Enum.map/2 .


As possibilidades



As palavras não custam nada, mostram o código!


Iteração, mapeamento, redução


 # 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} 

Aninhamento profundo → estrutura plana e 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]}} 

Pãezinhos


 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] 



As fontes estão abertas , a documentação é bastante detalhada , estamos em produção há quase dois anos.


Tenha uma boa iteração!

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


All Articles