Add `Access.at!`?

78 views
Skip to first unread message

Henrik N

unread,
Jul 6, 2020, 6:40:12 AM7/6/20
to elixir-lang-core
Hi,

I propose adding an `Access.at!` that raises when the given index is out of bounds, analogous to `Access.key!`.

The current `Access.at` is not nil safe as discussed in https://groups.google.com/g/elixir-lang-core/c/CvdW1FsvSf0/m/nlvxkThuEwAJ, but it's also not analogous to `Access.key!`. 

I specifically want it to raise and point to being out of range (or whatever the terminology is), rather than causing nil errors further down the line.

Current behaviour with `Access.at`:

# Happy case. Everything exists.
iex(20)> get_in([%{key: "value"}], [Access.at(0), :key])
"value"

# Nil safe when index is out of bounds.
iex(21)> get_in([%{key: "value"}], [Access.at(1), :key])
nil

# Not nil safe, but error doesn't point to the array index.
iex(22)> get_in([%{key: "value"}], [Access.at(1), Access.key!(:key)])
** (RuntimeError) Access.key!/1 expected a map/struct, got: nil
    (elixir 1.10.3) lib/access.ex:514: anonymous fn/4 in Access.key!/1

I propose something like this behaviour, with `Access.at!`:

# Happy case – behaves the same.
iex(20)> get_in([%{key: "value"}], [Access.at!(0), :key])
"value"

# Explodes.
iex(21)> get_in([%{key: "value"}], [Access.at!(1), :key])
** (FooError) index 1 not found in: [%{key: "value"}]. Its highest index is 0.

# Explodes in the same way.
iex(22)> get_in([%{key: "value"}], [Access.at(1), Access.key!(:key)])
** (FooError) index 1 not found in: [%{key: "value"}]. Its highest index is 0.

What do you think?

Henrik Nyh

unread,
Jul 6, 2020, 6:51:59 AM7/6/20
to elixir-lang-core
On 6 Jul 2020, 11:40 +0100, Henrik N <hen...@nyh.se>, wrote:

# Explodes in the same way.
iex(22)> get_in([%{key: "value"}], [Access.at(1), Access.key!(:key)])
** (FooError) index 1 not found in: [%{key: "value"}]. Its highest index is 0.

Oops, this example should have used `Access.at!` too, of course. 

And I probably didn’t use “nil safe” quite correctly in the proposal. But hopefully the idea comes across all the same.

José Valim

unread,
Jul 6, 2020, 6:58:03 AM7/6/20
to elixir-l...@googlegroups.com
Sounds good to me. Adding new bang variants are relatively straight-forward. Can you please send a PR? Also, for consistency reasons, can you please change Access.at/1 to become Access.at(index, default \\ nil)?

Thank you!

--
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/3a5d1489-dfa5-4c0c-bcd1-5cb85eac2f3fn%40googlegroups.com.

Henrik Nyh

unread,
Jul 6, 2020, 6:58:49 AM7/6/20
to elixir-l...@googlegroups.com
Great, thanks! Will try my hand at a PR :)
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/2bq8SlGVSZs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAGnRm4J%3DFZZiwmvAq9ELn2V8tYSmn0R%3D%2Bkt7J9U2HLZWCZDkgA%40mail.gmail.com.

Henrik Nyh

unread,
Jul 6, 2020, 11:48:51 AM7/6/20
to elixir-l...@googlegroups.com
I've started looking at Access.at(index, default \\ nil).

I guess it's fine for

list = [%{name: "john"}, %{name: "mary"}]
assert get_in(list, [Access.at(10, :my_default), :name])

to give a FunctionClauseError, since it ends up calling

Access.get(:my_default, :name, nil)

? Or should this situation be handled more gracefully? If so, how?




José Valim

unread,
Jul 6, 2020, 11:54:18 AM7/6/20
to elixir-l...@googlegroups.com
Correct. FunctionClauseError is expected here.

Henrik Nyh

unread,
Jul 12, 2020, 2:02:08 PM7/12/20
to elixir-lang-core
José found a tricky-to-resolve inconsistency and we ended up not adding a default parameter after all: https://github.com/elixir-lang/elixir/pull/10158

I have now started looking at Access.at!.

Should we also add Enum.at! for consistency? Looks straightforward. (Enum.at source: https://github.com/elixir-lang/elixir/blob/c192083726b5880ef3ed0b20ba01c124b73f3efe/lib/elixir/lib/enum.ex#L399-L405)

As for Access.at!, I think I'll either implement it similarly to the default parameter, passing some "should it return nil or should it explode" value to the private functions – or I'll just duplicate the code, since there's not much of it.

On Mon, Jul 6, 2020 at 4:54 PM José Valim <jose....@dashbit.co> wrote:
Correct. FunctionClauseError is expected here.

--
You received this message because you are subscribed to a topic in the Google Groups "elixir-lang-core" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elixir-lang-core/2bq8SlGVSZs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elixir-lang-co...@googlegroups.com.

José Valim

unread,
Jul 12, 2020, 2:17:16 PM7/12/20
to elixir-l...@googlegroups.com
We already have Enum.fetch!, which is why we don’t have Enum.at!

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.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/CAL%2B%3DsuMzhK0G8y%3DZDmPgiUJyqGJDBHZJLOUw5GBwSSYg4%3D8Drg%40mail.gmail.com.
Reply all
Reply to author
Forward
0 new messages