diff options
author | Andrew Bennett <potatosaladx@gmail.com> | 2018-04-30 08:11:37 -0500 |
---|---|---|
committer | Michał Muskała <michal@muskala.eu> | 2018-04-30 15:11:37 +0200 |
commit | 2ba20b6afada2359d08dcf5b516775f4bb26fbc8 (patch) | |
tree | 8d3e18fb1670dcb6a0908c300d27f4ba07820ce4 | |
parent | 55543b8826beab9c5fac0011be6fa22aa3854e03 (diff) | |
download | elixir-2ba20b6afada2359d08dcf5b516775f4bb26fbc8.tar.gz |
Remove throw/catch pattern for Keyword.delete/2 and friends (#7622)
This prevents stacktrace corruption.
Additionally short-circuit with `lists:keymember` for cases where the key
to delete is not in the list. It seems to be a relatively common case and
adds comparatively little overhead.
Benchmarks: https://gist.github.com/potatosalad/f4fea8490bea09eb000a3a346a52cb6e
-rw-r--r-- | lib/elixir/lib/keyword.ex | 64 |
1 files changed, 41 insertions, 23 deletions
diff --git a/lib/elixir/lib/keyword.ex b/lib/elixir/lib/keyword.ex index 13d14ffba..47e9c3390 100644 --- a/lib/elixir/lib/keyword.ex +++ b/lib/elixir/lib/keyword.ex @@ -463,19 +463,23 @@ defmodule Keyword do """ @spec delete(t, key, value) :: t def delete(keywords, key, value) when is_list(keywords) and is_atom(key) do - delete_key_value(keywords, key, value, _deleted? = false) - catch - :not_deleted -> keywords + case :lists.keymember(key, 1, keywords) do + true -> delete_key_value(keywords, key, value) + _ -> keywords + end end - defp delete_key_value([{key, value} | rest], key, value, _deleted?), - do: delete_key_value(rest, key, value, true) + defp delete_key_value([{key, value} | tail], key, value) do + delete_key_value(tail, key, value) + end - defp delete_key_value([{_, _} = pair | rest], key, value, deleted?), - do: [pair | delete_key_value(rest, key, value, deleted?)] + defp delete_key_value([{_, _} = pair | tail], key, value) do + [pair | delete_key_value(tail, key, value)] + end - defp delete_key_value([], _key, _value, _deleted? = true), do: [] - defp delete_key_value([], _key, _value, _deleted? = false), do: throw(:not_deleted) + defp delete_key_value([], _key, _value) do + [] + end @doc """ Deletes the entries in the keyword list for a specific `key`. @@ -497,18 +501,23 @@ defmodule Keyword do @spec delete(t, key) :: t @compile {:inline, delete: 2} def delete(keywords, key) when is_list(keywords) and is_atom(key) do - delete_key(keywords, key, _deleted? = false) - catch - :not_deleted -> keywords + case :lists.keymember(key, 1, keywords) do + true -> delete_key(keywords, key) + _ -> keywords + end end - defp delete_key([{key, _} | rest], key, _deleted?), do: delete_key(rest, key, true) + defp delete_key([{key, _} | tail], key) do + delete_key(tail, key) + end - defp delete_key([{_, _} = pair | rest], key, deleted?), - do: [pair | delete_key(rest, key, deleted?)] + defp delete_key([{_, _} = pair | tail], key) do + [pair | delete_key(tail, key)] + end - defp delete_key([], _key, _deleted? = true), do: [] - defp delete_key([], _key, _deleted? = false), do: throw(:not_deleted) + defp delete_key([], _key) do + [] + end @doc """ Deletes the first entry in the keyword list for a specific `key`. @@ -525,14 +534,23 @@ defmodule Keyword do """ @spec delete_first(t, key) :: t def delete_first(keywords, key) when is_list(keywords) and is_atom(key) do - delete_first_key(keywords, key) - catch - :not_deleted -> keywords + case :lists.keymember(key, 1, keywords) do + true -> delete_first_key(keywords, key) + _ -> keywords + end + end + + defp delete_first_key([{key, _} | tail], key) do + tail end - defp delete_first_key([{key, _} | rest], key), do: rest - defp delete_first_key([{_, _} = pair | rest], key), do: [pair | delete_first_key(rest, key)] - defp delete_first_key([], _key), do: throw(:not_deleted) + defp delete_first_key([{_, _} = pair | tail], key) do + [pair | delete_first_key(tail, key)] + end + + defp delete_first_key([], _key) do + [] + end @doc """ Puts the given `value` under `key`. |