[Proposal] Add macro is_real_map

75 views
Skip to first unread message

vtm

unread,
Dec 22, 2022, 1:25:37 AM12/22/22
to elixir-lang-core
hey all, thanks for you work on such a  beautiful lang.

Small proposal. People sometimes get confused about data structures which based on maps.
for instance:
```
iex(3)>  {:ok, datetime} = DateTime.now("Etc/UTC")
{:ok, ~U[2022-12-22 05:53:36.228720Z]}
iex(5)> is_struct(datetime)
true
iex(6)> is_map(datetime)
true
```
but here i got an error
```elixir
iex(11)> Enum.each(datetime, &Function.identity/1)
** (Protocol.UndefinedError) protocol Enumerable not implemented for ~U[2022-12-22 05:53:36.228720Z] of type DateTime (a struct)
```

i have in a codebase in function definition something like
`where is_map(map_name) and not is_struct(map_name)`
what i suggest to do is to create a macro `is_real_map` to check only real maps as a key-value storage with Enumerable impl
```elixir
  @doc """
  Returns true if `term` is a map and not is struct; otherwise returns `false`.

  Allowed in guard tests.

  ## Examples

      iex> is_real_map(%{oi: :blz})
      true

      iex> is_real_map(URI.parse("/"))
      false

  """
  @doc since: "1.16.0", guard: true
  defmacro is_real_map(term) do
    case __CALLER__.context do
      nil ->
        quote do
          case unquote(term) do
            %_{} -> false
            %{} -> true
            _ -> false
          end
        end

      :match ->
        invalid_match!(:is_real_map)

      :guard ->
        quote do
          is_map(unquote(term)) and not :erlang.is_map_key(:__struct__, unquote(term))
        end
    end
  end
```




Austin Ziegler

unread,
Dec 22, 2022, 10:22:30 AM12/22/22
to elixir-l...@googlegroups.com
I think that this is an interesting idea, but as Enumerable is a protocol, it is possible to implement enumerability for any given struct, as `MapSet` is enumerable.

This SO answer suggests using the Protocol.impl_for/1 callback instead. It can’t be used as a guard, but it gives a more accurate answer.

Whether `is_real_map/1` (or a different name) is more readable than `when is_map(term) and not is_struct(term)`, I can’t say because if I need to handle structs differently, I would generally use two different function heads (`def foo(term) when is_struct(term) do term |> Map.from_struct() |> foo() end` and `def foo(term) when is_map(term) do … end`).

-a

--
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/1f26dae3-9c67-446b-91d0-51bf1c5f3666n%40googlegroups.com.


--

Jay Rogov

unread,
Jan 3, 2023, 3:03:08 PM1/3/23
to elixir-lang-core
Also note that there's is_map_key/2 guard that serves the same purpose when used as `is_map_key(arg, :__struct__)`, allowing only maps but not structures:
https://hexdocs.pm/elixir/1.14.2/Kernel.html#is_map_key/2

Andrey Yugai

unread,
Jan 3, 2023, 11:34:42 PM1/3/23
to elixir-l...@googlegroups.com
There's also an option of pattern matching any struct, not exactly a guard, but changing your code a bit shouldn't be a problem

```

def foo(%_{} = _struct), do...

def foo(map) when is_map(map), do...

```






Sent from Proton Mail mobile




-------- Original Message --------
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-lang-core/90368793-c5b7-47d2-9dfd-f0ee6221f387n%40googlegroups.com.

Zach Daniel

unread,
Jan 4, 2023, 12:03:53 AM1/4/23
to elixir-l...@googlegroups.com
Wouldn't this suffice?

```
defguard is_real_map(x) when is_map(x) and not is_struct(x)
```


On Tue, Jan 03, 2023 at 11:34 PM, Andrey Yugai <and...@pm.me> wrote:
There's also an option of pattern matching any struct, not exactly a guard, but changing your code a bit shouldn't be a problem

```

def foo(%_{} = _struct), do...

def foo(map) when is_map(map), do...

```






Sent from Proton Mail mobile




-------- Original Message --------
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-lang-core+unsubscribe@googlegroups.com.
--
You received this message because you are subscribed to the Google Groups "elixir-lang-core" group.
Reply all
Reply to author
Forward
0 new messages