elixirmetaprogramming

How to create functions with Metaprogramming that have Struct Pattern Matching?


I am trying to create two functions :red_pieces and :black_pieces inside the module Board meant to get an element from the struct.

I want the compiled function to pattern match on %Board to it would take the argument %Board{red_pieces: pieces} for example. So this would look like:

def red_pieces(%Board{red_pieces: pieces}, n)
  case pieces do
    %{^n => b} -> {:ok, b}
    _ -> {:nil, :nil}
  end
end

Currently I can get it to work without pattern matching and just getting the struct fields in the function body like so:

defmodule Board do
  defstruct black_pieces: %{}, red_pieces: %{}

  # Creates functions: &Board.red_pieces/2 and &Board.black_pieces/2
  for fg <- [:red_pieces, :black_pieces] do
    def unquote(fg)(board = %__MODULE__{}, n) do
      case board.unquote(fg) do
        %{^n => b} -> {:ok, b}
        _ -> {:nil, :nil}
      end
    end
  end
end

However, instead of doing b = %__MODULE__{} and then case b.unquote(fg) I would like to pattern match on the value of fg so it would be like %__MODULE{red_pieces: pieces} , but I can't figure out how to do this.

Obviously, I could just create the two separate functions explicitly, but just for general learning purposes I was wondering if it was possible using metaprogramming.


Solution

  • You could use unquote on the key like %__MODULE__{unquote(fg) => pieces}:

      for fg <- [:red_pieces, :black_pieces] do
        def unquote(fg)(%__MODULE__{unquote(fg) => pieces}, n) do
          case pieces do
            %{^n => b} -> {:ok, b}
            _ -> {nil, nil}
          end
        end
      end
    

    %{black_pieces: pieces} is just syntactic sugar for %{:black_pieces => pieces}. You need to desugar it in order to use unquote, otherwise it won't be valid elixir syntax.