get_and_update_in is the solution here with :pop, but also using Access.key with a default of [] removes the need to special case the non existing key path:
{_, result} =
get_and_update_in(data, [:elements, Access.key(:dragon, [])], fn attributes ->
case List.delete(attributes, attribute) do
[] -> :pop
attributes -> {nil, attributes}
end
end)
If I were you, I will just implement the algorithm as closely as possible to your 3 bullet points. I believe someone said: “Don’t try to be as smart as possible in writing the code, because then you’d need to be smarter still the debug it”
case Map.fetch(data.elements, element) do
{:ok, attributes} ->
case List.delete(attributes, attribute) do
[] -> pop_in(data, [:elements, element])
attributes -> put_in(data, [:elements, element], attributes)
end
:error ->
data
end
So my solution would satisfy every requirement including Kernighan’s law
Funny also how devs prefer different styles.
Whenever I write case I’ll always think “stop right there” why are you not using the pipe operator.
If @LostKobrakai were in my team, I would tell him to stick to the assignment and please write readable code
I default to case statements because they match my mental model and are very expressive. Only when I have 3+ levels of cascading case statements and they become unwieldy, then I start thinking about refactoring into with or piping through private functions.
Yes case statements are very expressive, fully agree, (but) so are function heads.
I tend to better describe functions than cases even just naming a function feels like describing, while case oftentimes “hides” a moment of clarity, like later on you think huh!?
Anyway, I think it’s a good thing preferences exist.