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.
Why would you need a guard if you have impl_for? It serves the same function – checking whether something is a queryable, – as far as I understand your goal.
Ecto would raise if it receives something that doesn’t implement Ecto.Queryable … So is it really important to have it fail at the guard to your function instead of at some check within ecto?
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
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.