Updating a map's values that are lists

lists
maps

#1

What is the Elixir equivalent to Ruby’s Hash.new where you can specify a block that returns an empty list when you access a non-existent key (or Perl’s autovivification feature)?

The idea is that when you access a key in a Map that doesn’t exist you get back an empty list, which you can then append values to. And, if the key does exist, then you get back a non-empty list, which you can also append values to.

Also, why is it that the editor for this forum has a textbox that says “choose optional tags for this topic”, yet when I submit my question, I get an alert that says “You must choose at least 1 tag”, yet none of the tags are relevant to my question?


#2

In functional languages that kind of access is ‘inverted’, you specify the default arguments on the calls instead of the object, thus:

iex(1)> m = %{}
%{}
iex(2)> Map.get(m, :nope, [])
[]
iex(3)> Map.update(m, :nope, [6.28], &[ 6.28 |&1])
%{nope: [6.28]}

Which of course you can wrap up as new calls in your own custom module. :slight_smile:
Or do something generic like make a DefaultMap module that delegates to the normal Map module but sets an override default key in the map or something.
Lots of options. :slight_smile:

A relevant tag would just be ‘elixir’ or so, but adding tags is a good way to be able to search for topics in the future. :slight_smile:


#3

Thanks for the response.

A relevant tag would just be ‘elixir’ or so,

There is no elixir tag shown in the drop down list. And, when I select a tag in error, and I want to remove the tag, I try to click on the the ‘x’ in the tag, but that doesn’t work (in Safari 11.1).


#4

@AstonJ will need to look in to that, but do you get any error message in the javascript console or any error server responses in the network inspector?


#5

Yeah, that doesn’t do what Ruby’s Hash.new does when you specify a block (or Perl’s autovivification). The idea is that you can treat the value returned by a non-existent key the same way you do an existing key. What’s lacking is that Map.udpate/4 does not pass the default value to the function, which I think is a terrible oversight:

If key is not present in map , initial is inserted as the value of key . The initial value will not be passed through the update function

Here’s what Map.update/4 does:

iex(5)> data = %{a: [1, 2], b: [3]}
%{a: [1, 2], b: [3]}

iex(6)> Map.update(data, :c, [], &([10|&1]))
%{a: [1, 2], b: [3], c: []}

iex(7)> Map.update(data, :a, [], &([10|1]))
%{a: [10, 1, 2], b: [3]}

I think Map.update/4 should create the key c: [10] on line 6.

Here is what you can do in Ruby:

2.4.0 :011 => h = Hash.new {|hash, key| hash[key] = []}
=> {}

2.4.0 :012> h[:a] = [1, 2]
=> [1, 2]

2.4.0 :013> h
=> {:a=>[1, 2]}

2.4.0 :014> h[:a] << 10
=> [1, 2, 10]

2.4.0 :015> h[:b] << 10
=> [10]

2.4.0 :016 >; h
=> {:a=>[1, 2, 10], :b=>10]}

See how convenient that is? Perl also allows you to do something similar.


#6

I wish it did that as well, but you can always make your own function do it. :slight_smile:

For note, I’ve barely touched Ruby, I find it a very difficult to reason about language due to everything being in flux and so very untyped, so I don’t know what the defaults do, so overall it is better to explain what you want it to do rather than just say ‘this other language thing’ where not everyone knows that other language. ^.^;

I agree, I’ve always found that to be really weird, but I’ve chalked up a lot of Elixir’s weirdness to Ruby, guess it is the opposite this time. ^.^


#7

We generally don’t use ‘elixir’ as a tag since pretty much everything is related to Elixir on this forum :003: (and so I’ve made it a staff only tag)

I’ve added maps and lists as tags as they seem most relevant :023:


#8

Something else to ponder:

iex(1)> data = Map.new()
%{}
iex(2)> Map.fetch(data, :some_key)
:error
iex(3)> some_key = Access.key(:some_key, [])
#Function<4.27061494/3 in Access.key/2>
iex(4)> some_cons = fn (data, item) ->
...(4)>   update_in(data, [some_key], &([item|&1]))
...(4)> end
#Function<12.127694169/2 in :erl_eval.expr/5>
iex(5)> data = data |> some_cons.(:some_data) |> some_cons.(:more_data)
%{some_key: [:more_data, :some_data]}
iex(6)> Map.fetch(data, :some_key)
{:ok, [:more_data, :some_data]}
iex(7)> 

Kernel.update_in/3
Access.key/2


#9

Here are the javascript errors I get when I load the elixir forum page:

[Error] XMLHttpRequest cannot load https://elixirforum.com/message-bus/28cbf0220fed43f297d5f0b8e70b0b09/poll due to access control checks.
	send (ember_jquery-27e777857b8c0730dacfe09cb11711365d21a5db4f9ee0b85d494e4259cf6cda.js:3:16695)
	ajax (ember_jquery-27e777857b8c0730dacfe09cb11711365d21a5db4f9ee0b85d494e4259cf6cda.js:3:14212)
	n (vendor-6e59f8d0190b766ab492d48c83983f67bb7a5ecbff9e213f79fa12e4c72a06e6.js:1:19242)
	(anonymous function) (vendor-6e59f8d0190b766ab492d48c83983f67bb7a5ecbff9e213f79fa12e4c72a06e6.js:1:18300)
[Error] XMLHttpRequest cannot load https://elixirforum.com/message-bus/28cbf0220fed43f297d5f0b8e70b0b09/poll due to access control checks.
	send (ember_jquery-27e777857b8c0730dacfe09cb11711365d21a5db4f9ee0b85d494e4259cf6cda.js:3:16695)
	ajax (ember_jquery-27e777857b8c0730dacfe09cb11711365d21a5db4f9ee0b85d494e4259cf6cda.js:3:14212)
	n (vendor-6e59f8d0190b766ab492d48c83983f67bb7a5ecbff9e213f79fa12e4c72a06e6.js:1:19242)
	(anonymous function) (vendor-6e59f8d0190b766ab492d48c83983f67bb7a5ecbff9e213f79fa12e4c72a06e6.js:1:18300)
[Error] Failed to load resource: A server with the specified hostname could not be found. (analytics.js, line 0)

I don’t get any additional js errors when I try to “x out” a tag.

We generally don’t use ‘elixir’ as a tag since pretty much everything is related to Elixir on this forum

But the editor makes me choose at least one tag, and elixir is not a choice. So, should I choose the Ecto tag when my question has nothing to do with Ecto?


#10

Ahh, okay that works!

I’m wondering if something more generic like ‘language implementation’ or ‘stdlib’ might be good?


#11

The tags displayed change as you type and if the tag you want doesn’t exist, you can create it. So just tag a thread with what the thread is about :slight_smile:

The #language-implementation tag already exists and stdlib exists as #standard-library :slight_smile:


#12

Map.update(data, :c, [10], &([10|&1]))

I think this is one of those cases where the choice is arbitrary because either one will sometimes be the less convenient choice.


#13

Well Map.update is defined as update(map, key, initial, fun) and the comment says:

" If key is present in map with value value , fun is invoked with argument value and its result is used as the new value of key . If key is not present in map , initial is inserted as the value of key . The initial value will not be passed through the update function."

One reason for this is that there is no predefined default value for keys so assuming it is [] (or anything else for that matter) would be really weird as it pre-supposes that values have a specific type.


#14

It would be totally not in the spirit of Erlang & Elixir to have a default value associated with a map. But it would not be unreasonable, when key does not have an entry, to pass initial into fun. I prefer it the way it is, explicitly specifying initial, but the other way would not have been wrong.


#15

Who the heck proposed that the default value for a key should be an empty list? Please quote somebody.


#16

I don’t believe that at all. Perl is one of the, if not the, pre-eminent text processing languages. It has a feature called autovivification, where if you treat a value returned by a key as a list, then Perl creates the list for you and assigns it to the key, or if you treat the value returned by a key as a hash, then Perl creates a hash for you and assigns it to the key. I’ve never heard anyone in the Perl world say, “Boy, this autovivification really sucks.” Ruby recognized that autovivification was a great feature and incorporated it into Hash.new. And, as far as I can tell, Elixir is the rubification of erlang, so it makes sense to me that the great features of Ruby would be features in Elixir as well.


#17

You did not propose that exactly, but youd did mention the Ruby ability to set a default value on a per-map basis when the map is created.

(Later you called the fact that update does not pass the default to the fun a “terrible oversight” which I think is just wrong. As I said in an earlier post, it’s one of those choices where neither will be the most convenient all the time. And I prefer the way Elixir does it now because I think it’s more explicit what happens, one value for what happens if the key is not set, one function for what happens if the key is set, with no implicit connection between the two.)


#18

Elixir is not Perl. The phrase “if you treat a value returned by a key as a list” makes no sense in the Elixir context–you’re basically saying “if you expect of type XXX and you get nil, then automagically create an XXX and substitute it”, and that is exactly the kind of implicit magic that Elixir tries to avoid, by design.


#19

Elixir streams aren’t implicit magic? What about the Keylist syntax [a: 1, b: 2]? Or, the implicit magic involved in the syntax &(&1*&1)? From what I’ve seen, Elixir incorporates black magic frequently.

In any case, thanks to all who commented. I was searching for a feature that doesn’t exist, and I will have to make do.


#20

Don’t confuse syntactic sugar and implicit magic–the examples you post are all completely explicit.