Sometimes I want to check if the input into a function is not a blank string.
My first approach:
defmodule Example do
def do_stuff(string1, string2)
when is_binary(string1)
and byte_size(string1) > 0
and is_binary(string2)
and byte_size(string2) > 0 do
# Do some stuff with string1 and string2, because now we know they cannot be:
# * ""
# * " "
# * " "
end
end
The problem here is that when a string contains spaces(":space: ", “:space: :another_space:”, etc.) the guard clauses above will not detect it and I reach the body of the function, but what I want is to detect the blank string in the guard clause.
So I tried to make a custom guard:
defmodule BlankGuard do
defmacro is_not_blank?(string) do
callback = fn
<<" " :: binary, rest :: binary>>, func -> func.(rest, func)
_string = "", _func -> true
_string, _func -> false
end
is_blank = fn string, cb ->
cb.(string, cb)
end
quote do
unquote(string) |> is_binary()
and unquote(string) |> byte_size > 0
and unquote(string) |> is_blank.(callback)
end
end
end
And tried to use like this:
defmodule ExampleGuard do
import BlankGuard
def do_stuff(string1, string2) when is_not_blank?(string1) and is_not_blank?(string2) do
# Do some stuff with string1 and string2, because now we know they cannot be:
# * ""
# * " "
# * " "
end
end
And I get the error:
== Compilation error in file lib/play.ex ==
** (CompileError) lib/play.ex:81: invalid expression in guard, anonymous call is not allowed in guards. To learn more about guards, visit: https://hexdocs.pm/elixir/guards.html
Following the link in the error it seems that custom guards can only invoke other guards.
So my question is if I am missing something or if this is not really possible to implement in a guard clause?
I know I can implement this in each module I want to check for a blank string, but looks like unnecessary code repetition:
# @link https://rocket-science.ru/hacking/2017/03/28/is-empty-guard-for-binaries
def empty?(<<" " :: binary, rest :: binary>>), do: empty?(rest)
def empty?(string = ""), do: true
def empty?(_string), do: false