Using macros in guard clauses

Hey!
I have a function

@doc """
Check if given id has 64-bit size

Examples:
> MyModule.has_64_bit_size(132700114308300801)
true

> MyModule.has_64_bit_size(35)
false 
"""
def has_64_bit_size(id) do
  bit_size(:binary.encode_unsigned(id)) == 64    
end

I want to implement defmacro has_64_bit_size. What should it look like?

Something like this:

defmacro has_64_bit_size(id) do
  quote do
    bit_size(:binary.encode_unsigned(unquote(id))) == 64
  end    
end

I’m trying to thing of a use case where a macro would make more sense than just using the function you define but its not clear to me. Generally speaking, the first rule of macros in Elixir is don’t use macros. But of course when you need one, you need one!

Yes, this works well. Thanks.
But i forgot to say i want to use this macro in guard clause:

case decode_token(token) do
  {:ok, [id]} when is_integer(id) and has_64_bit_size(id) -> {:ok, id}
  _ -> {:error, :invalid_token}
end  

And it raises in compile time

cannot invoke remote function :binary.encode_unsigned/1 inside guards

So, as I understand it, the problem is that macros used in guard clauses should not have remote functions :thinking:

  1. https://keathley.io/blog/elixir-guard-clauses.html
  2. https://stackoverflow.com/questions/11177109/can-i-make-my-own-guards-in-erlang

Guards can only be composed of guard-safe functions and also with elixir >= 1.6 you want to use defguard instead of a macro to compose those. :binary.encode_unsigned/1 is not guard-safe though, so you cannot do what you want to do in a guard.

2 Likes

Since all you’re after is whether an int requires 64 bits, I think all you need is int > 9223372036854775807 since thats the maximum number that fits in 63 bits (if I’ve calculated correctly).

You do not need such magic value:

defguard at_most_64(num) when :erlang.band(num, :erlang.bsl(0xff, 56)) != 0 and num < :erlang.bsl(1, 64)
6 Likes

Oh yes, much better solution! Didn’t realise that bsl and friends were guard safe.