إيتابتور: مكتبة لإعادة رسم خريطة شفافة عميقة

هياكل بيانات إكسير غير قابلة للتغيير. هذا شيء رائع من وجهة نظر الاعتقاد بأن بياناتنا لن تتلف أكثر من الاعتراف في جزء آخر من التعليمات البرمجية غير ذات الصلة ، لكنها مزعجة بعض الشيء عندما نحتاج إلى تغيير بنية متداخلة بعمق.


لدينا مجموعة رائعة من Access ، والتي تبسط إلى حد كبير أربع عمليات أساسية على كائنات متداخلة بعمق باستخدام وظائف التصدير الافتراضية من Kernel :



عادةً ما تستخدم هذه الفرسان الأربعة (و D ' 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 ، تحتاج إلى معرفة المسار إلى العنصر الهدف ، ويتطلب كل هذا مقدارًا كبيرًا من التعليمات البرمجية لـ boilerplate من أجل تحديث العديد من القيم المتداخلة في وقت واحد (على سبيل المثال ، حذف جميع الأوراق ذات القيمة 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, ! 

انها حقا مريحة للغاية. ومع ذلك ، يصبح الرمز سريعًا مرهقًا عندما يتعلق الأمر بهياكل متداخلة بعمق ، مثل الخريطة ذات الكلمات الرئيسية المتداخلة ، والقوائم ، إلخ. مثال جيد على ذلك هو أي ملف تكوين يحتوي على أقسام فرعية متداخلة.


عدد الأسئلة في Stack Overflow مع السؤال "كيف يمكنني تغيير البنية المتداخلة؟" أجبرني على إنشاء هذه المكتبة أخيرًا. يبدو التنفيذ في Elixir مربكًا بعض الشيء ، لأن كل شيء حوله غير قابل للتغيير ، ولا يمكنك فقط النزول إلى فروع الهيكل حتى الأوراق ، وتغيير كل ما تحتاجه في المكان. ستحتاج إلى بطارية ، مع ذلك ، تحت أي غطاء للرمز الوظيفي. من المحتمل أن يكون تغيير الهياكل المتداخلة هو المثال الوحيد الذي رأيته في حياتي عندما يسهل التغيير.


كمكافأة لسلائف الخريطة العادية ، أضفت تطبيقًا لتخزين القيمة بعمق داخل الهيكل ، مما يؤدي إلى إنشاء مفاتيح وسيطة حسب الحاجة. يتصرف كما هو مقترح ، ولكنه مرفوض في قلب الياقوت Hash#bury . تعرف هذه المكتبة أيضًا كيفية "إنشاء jasonize" لهياكل متداخلة تحتوي على كلمات رئيسية لا يمكن تسلسلها ببساطة في json ، لأنه داخلها يتم تقديمها كقوائم تحتوي على عنصرين tuples ( [foo: :bar] == [{:foo, :bar}] ) ، و tuples ليست قابلة للتسلسل من خارج منطقة الجزاء.




لذلك ، دعنا نحيي المكتبة ، التي تتكرر على أي خريطة / كلمة أساسية / قائمة في الذيل Enum.each/2 بسيطة مثل Enum.each/2 القياسية و Enum.map/2 .


الاحتمالات


  • Iteraptor.each/3 تكرار بسيط ، بإرجاع البنية نفسها ؛
  • تعيين Iteraptor.map/3 وإرجاع بنية المعينة؛
  • Iteraptor.reduce/4 تقليل ، إرجاع البطارية ؛
  • Iteraptor.map_reduce/4 الخريطة وإعادة تعيينها tuple بنتيجة التعيين والبطارية ؛
  • Iteraptor.filter/3 بتصفية الهيكل باستخدام الوظيفة التي حصلت عليها المعلمة الأخيرة ؛
  • Iteraptor.jsonify/2 بإعداد بنية التسلسل في json : يتم استبدال جميع الكلمات الأساسية بالخرائط ، ويتم تحويل المفاتيح إلى سلاسل.
  • يكتب Iteraptor.Extras.bury/4 القيمة في عمق البنية ، مع إنشاء مفاتيح وسيطة حسب الحاجة ؛
  • Iteraptor.to_flatmap/2 بتحويل البنية إلى بنية مسطحة ، مما يؤدي إلى إنشاء فهارس للقوائم بعناية ؛ يمكن الحصول على الهيكل الأصلي مرة أخرى باستخدام
  • يتحول Iteraptor.from_flatmap/3 إلى بنية الفصيلة ذات المفاتيح المتسلسلة إلى بنية متداخلة بعمق ؛
  • use Iteraptor.Iteraptable التنفيذ التلقائي Enumerable للبروتوكولات Enumerable ، بالإضافة إلى سلوك Access للبنية. يوجد وصف أكثر تفصيلًا لما يوجد تحت الغطاء.

الكلمات لا تكلف شيئا ، وتظهر الكود!


التكرار ، رسم الخرائط ، والحد


 # 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/ar482478/


All Articles