Reputation: 1473
I want to achieve something like the below (which obviously does not work)
def f1(%{key => _}) when is_integer(key) do :error end
def f1(%{}) do :ok end
I could match a map in the function head and check for an integer key inside the function body. Just wondering if there is a better approach. Any suggestions?
Upvotes: 3
Views: 2495
Reputation: 121000
While it’s impossible per se, one might slightly improve the performance by generating the clauses when possible integer values are known in advance. Also, Kernel.map_size/1
, which is allowed in guards, will be required:
defmodule NoIntegerKeys do
defmacro __using__(name: name, disallowed: enum, do: block) do
[
(quote do: def unquote(name)(%{} = map) when map_size(map) == 0, do: :ok) |
Enum.map(enum, fn i ->
quote do
def unquote(name)(%{unquote(i) => _}), do: :error
end
end)
] ++ [
(quote do: def unquote(name)(%{} = map), do: unquote(block))
]
end
end
defmodule Test do
use NoIntegerKeys, name: :f1, disallowed: [0,1,2], do: :ok
end
Test.f1(%{})
#⇒ :ok
Test.f1(%{foo: :bar})
#⇒ :ok
Test.f1(%{:foo => :bar, 3 => :baz})
#⇒ :ok
# BUT
Test.f1(%{:foo => :bar, 2 => :baz})
#⇒ :error
This example is a bit contrived, but it shows how one fight for the performance when it’s really needed. Macros are expanded by the compiler, so the resulting code would declare 5 different clauses: one for an empty map, three for possible values and the last one for the default execution block.
Upvotes: 2
Reputation: 222198
Nope, this can't be done with pattern matching. Here's how I'd implement it using the method you described:
def f1(%{} = map) do
if map |> Map.keys() |> Enum.any?(&is_integer/1), do: :error, else: :ok
end
Upvotes: 3