So this is my first attempt at creating a macro, so I’m sure I’m missing something dumb, but I couldn’t find anything via searching.
I have a function that can return a list with different type of values depending on what was passed in. However, for my purposes I don’t care what order the values exist in the list, I just need to test that it has all the values that I expect it to have. 99% of the time ordering won’t matter and it can cause my tests to become brittle for little gain.
So I decided to make a macro with the idea that it can iterate over a list performing the specified match against each element. If at least one matches then we are good but if none match I want to raise an error.
So far I have come up with the following code:
defmodule ListAssertions do
defmacro contains([], match_expression) do
string_representation = Macro.to_string(match_expression)
quote do
raise("No entries in the list matched the expression: #{unquote(string_representation)}")
end
end
defmacro contains([head | tail], match_expression) do
quote do
try do
unquote(head) = unquote(match_expression) # Don't continue if we found a match
rescue
MatchError -> contains(tail, match_expression)
end
end
end
end
This works fine when an empty list is provided (e.g. ListAssertions.contains([], 1)) but this fails when a non-empty list is provided (e.g. ListAssertions.contains([1,2], 1)) with the following error:
warning: variable tail is unused
list_assertions.exs:10
** (CompileError) list_assertions.exs:25: undefined function contains/2
(stdlib) lists.erl:1337: :lists.foreach/2
list_assertions.exs:21: (file)
(elixir) lib/code.ex:363: Code.require_file/2
I’m assuming this is because it’s expanding into the literal call contains() function call, which can’t be resolved at final compile time, since after expansion there is no function called contains.
However, I’ve been unable to figure out the right way to resolve this. Anyone have any insight into how to write this?




















