Iteraptor:用于深层透明地图重做的库

Elixir数据结构是不可变的。 从这样的观点来看,这是很棒的,即我们的数据不会在其他不相关的代码段中被破坏而无法识别,但是当我们需要更改深层嵌套的结构时,这有点烦人。


我们有一个出色的Access抽象,使用从Kernel导出的默认函数,极大地简化了深层嵌套对象的四个基本操作:



这四个火枪手(和d'Artagnan Kernel.get_and_update_in/{2,3}通常是这样使用的:


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

它很方便,并且在许多情况下都可以使用...除了不起作用的情况以外。 要使用Access ,您需要知道目标元素的路径,并且所有这些都需要大量的样板代码才能一次更新多个嵌套值(例如,删除所有值为nil叶子,或对不需要在日志中显示的所有字段的内容nil星标)。


为了提供使用嵌套结构的批发折扣,创建了Iteraptor库。


TL; DR:





迭代您可以在Elixir进行神奇迭代的所有内容。 要使其可迭代,只需为此特定类型实现Enumerable协议。 您可以将通道分为管道,映射,缩小,过滤,稀疏...原谅我的法语,是的。 每个在Elixir上呆了至少八个小时的人肯定都看到(甚至可能写了)这样的东西:


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

真的很方便。 但是,当涉及到深度嵌套的结构(例如带有嵌套关键字 ,列表等的映射)时,代码很快变得很麻烦。任何包含嵌套子节的配置文件就是一个很好的例子。


关于堆栈溢出的问题数量为“如何更改嵌套结构?”,这迫使我最终创建此库。 在Elixir上的实现似乎有些混乱,因为周围的一切都是不可变的,您不能仅仅顺着结构的分支向下走,就可以改变所需的一切。 但是,您将需要一块电池,就像在任何功能代码中一样。 当可变性使事情变得更容易时,改变嵌套结构可能是我一生中唯一看到的例子。


作为常规地图前身的一项奖励,我添加了一个实现来将值存储在结构内部深处,该实现根据需要创建中间键。 它的行为与建议一致,但在红宝石核心Hash#bury被拒绝。 该库还知道如何“嵌套”包含无法简单地在json中序列化的关键字的嵌套结构,因为它们在内部以两个元素的元组的列表( [foo: :bar] == [{:foo, :bar}] )表示,和元组不可开箱即用。




因此,让我们来迎接该库,该库在尾部和鬃毛上的任何map / keyword / list上进行迭代,几乎与标准Enum.each/2Enum.map/2一样简单。


可能性



话不花钱,显示代码!


迭代,映射,减少


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

深层嵌套→扁平结构,反之亦然


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

包子


 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] 



来源是开放的 ,文档非常详细 ,我们已经投入生产将近两年了。


祝您迭代愉快!

Source: https://habr.com/ru/post/zh-CN482478/


All Articles