dimitarvp
Can a defguard definition match against a struct?
Hello,
Is it possible to check if an argument to a guard is a particular structure?
Context: I am trying to have a defguard definition that checks if something is an Ecto query – which means it can either be a module (e.g. atom) or it can be an %Ecto.Query{}. But I cannot seem to find a way to do it via defguard.
defmodule A do
defguard is_query(q) when is_atom(q) or ... # <- what do we put here?
end
Tried with match?(%Ecto.Query{}, q) but that did not compile.
Something I am missing?
Marked As Solved
msimonborg
The simpler way to do this now is defguard is_query(a) when is_struct(a, Ecto.Query) using Kernel.is_struct/2, which was introduced after this thread was last active. PR #9470
As for your question, I am not 100% sure, but if what you have created is an Ecto.Query struct, it will match
Also Liked
idi527
As for your actual question:
defguard is_query(a) when is_map(a) and :erlang.map_get(:__struct__, a) == Ecto.Query
dimitarvp
Ha, I like this a lot. Thanks for pointing it out.
wojtekmach
It is impossible to do in a defguard. I think it is possible, with big caveats so maybe actually impossible too, with a defmacro. If the protocol is consolidated, you can grab the list of modules implementing it. So if your protocol is consolidated by the time you call your guard, which generally may not necessarily be the case, that should work. Here’s a sketch:
defmodule Macros do
defmacro is_impl(term, protocol) do
protocol = Macro.expand(protocol, __ENV__)
impls =
case protocol.__protocol__(:impls) do
{:consolidated, impls} ->
impls
:not_consolidated ->
raise "#{inspect(protocol)} is not consolidated"
end
quote do
unquote(term).__struct__ in unquote(impls)
end
end
end
defmodule Foo do
import Macros
def f(enumerable) when is_impl(enumerable, Enumerable) do
Enum.to_list(enumerable)
end
end
besides the caveat around consolidation time, in the list of impls we have things like Atom, Integer, …, Any, and they all would have to be handled too.
So yeah, not worth it. ![]()







