I’m wondering if it’s possible to pass a pattern to a macro (I assume I need a macro for this, which may be wrong!)
Basically, I want to write a function that would be called like:
iex> remove_newline_and_match_parts("a b c d e\n", ["a", _, _, _, _])
{:ok, ["a", "b", "c", "d", "e"]}
I’m attempting to implement this by writing a separate macro split_spaces_and_match(), and that’s where I’m having trouble, because I’m able to implement that sub-macro as:
defmacro split_spaces_and_match(data, pattern) do
quote do
result = String.split(unquote(data), " ")
case result do
unquote(pattern) = ret -> {:ok, ret}
_ -> {:error, :pattern_match_failed}
end
end
end
But then when I try to call that macro from remove_newline_and_match_parts(), things are being quoted differently and I’m very confused.
Usually you’ll want to use functions for sub-parts of a complex macro, to avoid extra levels of quoting - try changing defmacro split_spaces_and_match to def split_spaces_and_match.
I’m confused, I took the code that you write and it literally worked as is:
defmodule Foo do
defmacro remove_newline_and_match_parts(data, pattern) do
quote do
result = String.split(unquote(data), " ")
case result do
unquote(pattern) = ret -> {:ok, ret}
_ -> {:error, :pattern_match_failed}
end
end
end
end
import Foo
remove_newline_and_match_parts("a b c d e\n", ["a", _, _, _, _])
{:ok, ["a", "b", "c", "d", "e\n"]}
If you want to remove the new line you should also String.trim in addition to the String.split and you’re done.
All I had to do was rename the macro to be what you call. What issue are you running into?
Sorry, should clarify: I want to return {:error, :no_newline} if there’s a missing newline and {:error, :pattern_match_failed} if the match fails after removing the newline, so I’m doing something beyond String.trim(). I’d also like to be able to call just split_spaces_and_match() on its own, so I thought both need to be macros in order to accept a pattern.
defmacro remove_newline_and_match_parts(string, pattern) do
quote do
with :ok <- unquote(__MODULE__).check_trailing_newline?(unquote(string)) do
split_spaces_and_match(unquote(string), unquote(pattern))
end
end
end
def check_trailing_newline?(string) do
# check that the string has a trailing new line.
end
You only need one layer of defmacro to do that. For instance:
# the definition
defmacro remove_newline_and_match_parts(data, pattern) do
# in here, "pattern" is the AST of the pattern
nope(pattern)
yep(pattern)
end
defmacro nope(pattern) do
# pattern is the AST of accessing the local variable named "pattern"
# this is likely NOT what you want!
end
def yep(pattern) do
# pattern is still the AST of the pattern passed to remove_newline_and_match_parts
end
...
# the callsite
remove_newline_and_match_parts("a b c d e\n", ["a", _, _, _, _])
A frequent idiom for dealing with things like this - the defmacros for both remove_newline_and_match_parts and split_spaces_and_match juggle their arguments a little and then call the same private function to turn those arguments into output AST.