Map operation on recursive hierarchical structure (Composite pattern)

Are you looking for something like this?

iex(7)> posts                                                                                  
[
  %{"has_footnote" => false, "label" => "My Title Without Asterisk"},
  %{"has_footnote" => false, "label" => "My Title*"}
]
iex(8)> for %{"label" => title} = p <- posts do                                                
...(8)>   if String.ends_with?(title, "*") do                                         
...(8)>     %{p | "label" => String.trim_trailing(title, "*"), "has_footnote" => true}
...(8)>   else
...(8)>     p                                                                         
...(8)>   end
...(8)> end
[
  %{"has_footnote" => false, "label" => "My Title Without Asterisk"},
  %{"has_footnote" => true, "label" => "My Title"}
] 

Note that you don’t have to match the “label” key or even the fact that it’s a map. I just did it so that I could use title without writing p.title, but the risk here is that if you have a badly formed collection of posts and some of them don’t have the "label" key, this actually acts as a filter, which is likely not what you want.

The correct thing, of course, is to not be able to create a post struct like this without having these keys. You can do this using defstruct and the @enforce_keys module attribute (or using Vex, Ecto, etc. to have validation and required fields some other way):

defmodule MyApp.Post do
  @fields [:label, :has_footnote]
  defstruct @fields
  @enforce_keys @fields
end

Edit: Of course @kokolegorille has it: You need to simply call the transformation on the list of children you have as well, and as long as they have the needed keys you’ll be fine.

2 Likes