put_in and lists (and access)

430 views
Skip to first unread message

Alexei Sholik

unread,
Jun 17, 2014, 12:25:11 PM6/17/14
to elixir-lang-core
I have this nasty piece of code that could be much simplified using put_in if there wasn't a list in a nested data structure.

This is what I have working:

spec = %{
  commands: [
    %{name: "abc", ...},
    %{name: "def", ...},
    ...
  ]
}

...

# we have {key, val} defined
index = Enum.find_index(spec.commands, &(&1.name == key))
if !index, do: config_error("Unrecognized command: #{key}")

cmdspec = Enum.at(spec.commands, index)
Map.update!(spec, :commands, fn list ->
  List.replace_at(list, index, Map.put(cmdspec, :action, val))
end)

What I would love to be able to write instead:

index = Enum.find_index(spec.commands, &(&1.name == key))
if !index, do: config_error("Unrecognized command: #{key}")

put_in(spec.commands<[index]>[:action], val)

where <[index]> is a hypothetical indexing operation.

I could actually make the following work

put_in(spec.commands[index][:action], val)

if I converted 'commands' into a map that looks like this: %{ 0 => %{...}, 1 => %{...} }. But that seems too ugly, I'd rather go with the first approach then.

Any comments?

---
Best regards
Alexei Sholik

Alexei Sholik

unread,
Jun 17, 2014, 12:25:11 PM6/17/14
to elixir-lang-core
Here are the same code fragments in a gist for easier reading https://gist.github.com/alco/268fcf29a9c439b92fa9
--
Best regards
Alexei Sholik

José Valim

unread,
Jun 17, 2014, 12:49:22 PM6/17/14
to elixir-l...@googlegroups.com
I have run into a similar issue and used update_in:

update_in spec.commands, &update_command(&1, key, val)

def update_command(commands, key, val) do
  ...
end

It is not ideal but it was the best approach I could come up with.

I don't plan to support index access for lists due to the bad nature of accessing a linked list by index. Maybe it doesn't matter for the example above but for large lists the best would to be to enumerate it once:

def update_command(commands, key, val) do
  {commands, found} = Enum.map_reduce(commands, false, fn
    %{name: name} = map, _found when name == key ->
      {%{map | action: value}, true}
    other, found ->
      {other, found}
  end
  unless found do
    config_error(...)
  end
  commands
end

However the code above is more complex than the alternative that does index access, which makes me unhappy.

It is interesting because I would like to introduce vectors that works like lists but can be efficiently accessed by index. However vectors are not getting in the language before full maps are implemented in Erlang (as some decisions depends on the implementation and I have a hunch that maps may be the best option).

Another solution may be to have better for comprehensions or even loops, as map_reduce above is quite verbose, but I am not sure if they would help in this particular example.






José Valim
Skype: jv.ptec
Founder and Lead Developer


--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-co...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply all
Reply to author
Forward
0 new messages