Define a custom guard using Macro

Hi, i’m currently writing version 2 of Mizur.

Now, using the Mizur module in another module generates the entire API in the receiver module. This generally avoids dependence on two modules in use.
The Mizur.System module exposes several macros, including the macro type name = ... that creates several functions in the receiver module.

I would like to take advantage of this moment to create a specific guard.
Currently I have this code:

defmodule Mizur.System do
   defmacro __using__(_opts) do 
    quote do 
      import Mizur.System
      # here some uncontextual code ! 
      # This code is included into the macro "type name = ..."
      is = String.to_atom("is_" <> Atom.to_string(name))
      defmacro unquote(is)(e) do
        mod = __MODULE__
        typ = Module.concat(mod, Type)
        n = unquote(name)
        quote do
          case unquote(e) do
            %mod{type: %typ{name: unquote(n)}} -> true
            _ -> false
          end
        end
      end

      # .. the rest of the code, uncontextual to ! 

If I use the code in a simple expression, it will work, but if I write somethings like that :

test "for macros" do 
    import Length
    c = cm(12)

    IO.inspect is_cm(c) # This code is valid.

    case c do 
      x when is_cm(x) -> IO.inspect "lol"
      _ -> IO.inspect "snif"
    end 

I have this output :

** (CompileError) test/mizur_test.exs:112: invalid expression in guard
    expanding macro: MizurTest.Length.is_cm/1
    test/mizur_test.exs:112: MizurTest."test for macros"/1
    (elixir) lib/code.ex:370: Code.require_file/2
    (elixir) lib/kernel/parallel_require.ex:57: anonymous fn/2 in Kernel.ParallelRequire.spawn_requires/5

I do not know if I made any mistake.

Regards, Xavier !

Afair you can neither access maps in guards nor you can use a case

Ok, thanks !

Yeah if you are making an is_* guard then you can only use other guards inside it. Take a look at my defguard library to see how I generate guards.

(EDIT: Do note, the entire code of that library is a huge hack that overwrites def and defp and so forth and should not be used as an example of what to do except for the guard part itself, that library is purely an example to see ‘if I could do something’, not ‘should’ ^.^)

1 Like

Is it still true that you can’t access maps in guards? I have this and it works okay:

defmodule XXXX.Guards do
  # Check if a value has the production live mode.
  defguard is_production(value)
           when is_map(value) and
                  is_boolean(value.live_mode) and
                  value.live_mode == true

  # Check if a value has the sandbox live mode.
  defguard is_sandbox(value)
           when is_map(value) and
                  is_boolean(value.live_mode) and
                  value.live_mode == false
end
defmodule XXXXX.GuardsTest do
  use ExUnit.Case, async: true

  require XXXXX.Guards
  alias XXXXX.Guards

  describe "is_production/1" do
    test "identifies a production value" do
      assert Guards.is_production(%{live_mode: true})
    end

    test "identifies a sandbox value" do
      refute Guards.is_production(%{live_mode: false})
    end
  end

  describe "is_sandbox/1" do
    test "identifies a production value" do
      refute Guards.is_sandbox(%{live_mode: true})
    end

    test "identifies a sandbox value" do
      assert Guards.is_sandbox(%{live_mode: false})
    end
  end
end

It‘s no longer the case. 1.10 added support for the map guards OTP added in version 21.

4 Likes