Permalink
Browse files

Access is no longer a protocol

  • Loading branch information...
josevalim committed Aug 10, 2015
1 parent ce37c36 commit af7c09e0c19974117b26ec0340b9a2dbb9d36ae9
Showing with 109 additions and 109 deletions.
  1. +94 −80 lib/elixir/lib/access.ex
  2. +0 −11 lib/elixir/lib/hash_dict.ex
  3. +8 −8 lib/elixir/lib/protocol.ex
  4. +7 −10 lib/elixir/test/elixir/access_test.exs
View
@@ -1,23 +1,15 @@
defprotocol Access do
defmodule Access do
@moduledoc """
Dictionary-like access to data structures via the `foo[bar]` syntax.
This module also empowers `Kernel`s nested update functions
`Kernel.get_in/2`, `Kernel.put_in/3`, `Kernel.update_in/3` and
`Kernel.get_and_update_in/3`.
## Deprecated
Currently, the Access protocol is deprecated as there are performance
concerns in the current implementation. Since Elixir v1.1, instead of
using a protocol, `foo[bar]` will dispatch directly to the `Dict`
module. Therefore, while `foo[bar]` will continue to work, extension
of the syntax should be done via a custom `Dict` implementation.
## Examples
Out of the box, Access works all built-in dictionaries: `Keyword`,
`Map` and `HashDict`:
Out of the box, Access works with built-in dictionaries: `Keyword`
and `Map`:
iex> keywords = [a: 1, b: 2]
iex> keywords[:a]
@@ -40,11 +32,83 @@ defprotocol Access do
The key comparison must be implemented using the `===` operator.
"""
use Behaviour
@type t :: list | map | nil
@type key :: any
@type value :: any
defcallback fetch(t, key) :: {:ok, value} | :error
defcallback get(t, key, value) :: value
defcallback get_and_update(t, key, (value -> {value, value})) :: {value, t}
@doc """
Fetches value for the given key.
Returns a `{:ok, value}` if the tuple exists,
`:error` otherwise.
"""
@spec fetch(t, key) :: {:ok, value} | :error
def fetch(container, key)
def fetch(%{__struct__: struct} = container, key) do
struct.fetch(container, key)
end
def fetch(%{} = map, key) do
:maps.find(key, map)
end
def fetch(list, key) when is_list(list) do
case :lists.keyfind(key, 1, list) do
{^key, value} -> {:ok, value}
false -> :error
end
end
def fetch(nil, _key) do
:error
end
@doc """
Fetches value for the given key.
Returns `value` or raises otherwise.
"""
def fetch!(dict, key) do
case fetch(dict, key) do
{:ok, value} -> value
:error -> raise KeyError, key: key, term: dict
end
end
@doc """
Gets the container's value for the given key.
"""
@spec get(t, term) :: t
def get(container, key)
@spec get(t, term, term) :: term
def get(container, key, default \\ nil)
def get(%{__struct__: struct} = container, key, default) do
struct.get(container, key, default)
end
def get(%{} = map, key, default) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> default
end
end
def get(list, key, default) when is_list(list) do
case :lists.keyfind(key, 1, list) do
{^key, value} -> value
false -> default
end
end
def get(nil, _key, default) do
default
end
@doc """
Gets and updates the container's value for the given key, in a single pass.
@@ -59,58 +123,34 @@ defprotocol Access do
"""
@spec get_and_update(t, term, (term -> {get, term})) :: {get, t} when get: var
def get_and_update(container, key, fun)
end
defimpl Access, for: List do
def get(dict, key) when is_atom(key) do
case :lists.keyfind(key, 1, dict) do
{^key, value} -> value
false -> nil
end
end
def get(_dict, key) do
raise ArgumentError,
"the access protocol for lists expect the key to be an atom, got: #{inspect key}"
def get_and_update(%{__struct__: struct} = container, key, fun) do
struct.get_and_update(container, key, fun)
end
def get_and_update(dict, key, fun) when is_atom(key) do
get_and_update(dict, [], key, fun)
end
def get_and_update(%{} = map, key, fun) do
current_value = case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
defp get_and_update([{key, value}|t], acc, key, fun) do
{get, update} = fun.(value)
{get, :lists.reverse(acc, [{key, update}|t])}
{get, update} = fun.(current_value)
{get, :maps.put(key, update, map)}
end
defp get_and_update([h|t], acc, key, fun) do
get_and_update(t, [h|acc], key, fun)
def get_and_update(list, key, fun) when is_list(list) do
Keyword.get_and_update(list, key, fun)
end
defp get_and_update([], acc, key, fun) do
{get, update} = fun.(nil)
{get, [{key, update}|:lists.reverse(acc)]}
def get_and_update(nil, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end
end
defimpl Access, for: [Map, Any] do
def get(map, key) do
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
end
def get_and_update(map, key, fun) do
value =
case :maps.find(key, map) do
{:ok, value} -> value
:error -> nil
end
{get, update} = fun.(value)
{get, :maps.put(key, update, map)}
end
# Callbacks invoked when inlining code for *_in in Kernel.
defmodule Access.Map do
@moduledoc false
def get!(%{} = map, key) do
case :maps.find(key, map) do
@@ -139,29 +179,3 @@ defimpl Access, for: [Map, Any] do
"could not put/update key #{inspect key}. Expected map/struct, got: #{inspect other}"
end
end
defimpl Access, for: Atom do
def get(nil, _) do
nil
end
def get(atom, _) do
undefined(atom)
end
def get_and_update(nil, key, _fun) do
raise ArgumentError,
"could not put/update key #{inspect key} on a nil value"
end
def get_and_update(atom, _key, _fun) do
undefined(atom)
end
defp undefined(atom) do
raise Protocol.UndefinedError,
protocol: @protocol,
value: atom,
description: "only the nil atom is supported"
end
end
@@ -233,17 +233,6 @@ defimpl Enumerable, for: HashDict do
def count(dict), do: {:ok, HashDict.size(dict)}
end
defimpl Access, for: HashDict do
def get(dict, key) do
HashDict.get(dict, key, nil)
end
def get_and_update(dict, key, fun) do
{get, update} = fun.(HashDict.get(dict, key, nil))
{get, HashDict.put(dict, key, update)}
end
end
defimpl Collectable, for: HashDict do
def into(original) do
{original, fn
View
@@ -542,14 +542,14 @@ defmodule Protocol do
for = unquote(for)
name = Module.concat(protocol, for)
# TODO: Emit warnings once we reimplement Access before 1.1
# if protocol == Access do
# :elixir_errors.warn __ENV__.line, __ENV__.file,
# "implementation of the Access protocol is deprecated. For customization of " <>
# "the dict[key] syntax, please implement the Dict behaviour instead"
# end
Protocol.assert_protocol!(protocol)
# TODO: Remove this by 1.2
if protocol == Access do
:elixir_errors.warn __ENV__.line, __ENV__.file,
"implementation of the Access protocol is deprecated. For customization of " <>
"the dict[key] syntax, please implement the Dict behaviour instead"
else
Protocol.assert_protocol!(protocol)
end
defmodule name do
@behaviour protocol
@@ -19,6 +19,7 @@ defmodule AccessTest do
test "for nil" do
assert nil[:foo] == nil
assert Access.fetch(nil, :foo) == :error
assert Access.get(nil, :foo) == nil
assert_raise ArgumentError, "could not put/update key :foo on a nil value", fn ->
Access.get_and_update(nil, :foo, fn nil -> {:ok, :bar} end)
@@ -30,6 +31,9 @@ defmodule AccessTest do
assert [foo: [bar: :baz]][:foo][:bar] == :baz
assert [foo: [bar: :baz]][:fuu][:bar] == nil
assert Access.fetch([foo: :bar], :foo) == {:ok, :bar}
assert Access.fetch([foo: :bar], :bar) == :error
assert Access.get([foo: :bar], :foo) == :bar
assert Access.get_and_update([], :foo, fn nil -> {:ok, :baz} end) == {:ok, [foo: :baz]}
assert Access.get_and_update([foo: :bar], :foo, fn :bar -> {:ok, :baz} end) == {:ok, [foo: :baz]}
@@ -41,18 +45,11 @@ defmodule AccessTest do
assert %{1.0 => 1.0}[1.0] == 1.0
assert %{1 => 1}[1.0] == nil
assert Access.fetch(%{foo: :bar}, :foo) == {:ok, :bar}
assert Access.fetch(%{foo: :bar}, :bar) == :error
assert Access.get(%{foo: :bar}, :foo) == :bar
assert Access.get_and_update(%{}, :foo, fn nil -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
assert Access.get_and_update(%{foo: :bar}, :foo, fn :bar -> {:ok, :baz} end) == {:ok, %{foo: :baz}}
end
test "for atoms" do
assert_raise Protocol.UndefinedError, ~r"protocol Access not implemented for :foo", fn ->
Access.get(:foo, :bar)
end
assert_raise Protocol.UndefinedError, ~r"protocol Access not implemented for :foo", fn ->
Access.get_and_update(:foo, :bar, fn _ -> {:ok, :baz} end)
end
end
end

0 comments on commit af7c09e

Please sign in to comment.