title: Proper way to implement (Mnesia-style / wildcard / don't care) tuple matching (Erlang)
body: <p>I'm implementing a Chess game (Chinese Chess, aka. Xiangqi, to be exact) in Erlang.</p>
  
  <p>A piece is represented by a <code>{Color, Type}</code> tuple, and a point (ie. location) is represented by a <code>{File, Rank}</code> tuple. The board is represented by a point-to-piece map (ie. <code>#{point() =&gt; piece()}</code>).</p>
  
  <p>There is a function to query whether a particular point on the board is occupied by a piece or not:</p>
  
  <pre><code>is_point_occupied_simple(Board, Point) -&gt;
      ensure_is_point(Point),
      case maps:find(Point, Board) of
          {ok, _} -&gt;
              true;
          error -&gt;
              false
      end.
  </code></pre>
  
  <p>However, I would like to add an optional parameter to check the color of the piece - if the point is occupied by a piece of the specified color, the function returns true; otherwise it returns false. If I don't care about the color of the piece, I can just put <code>'_'</code> in the <code>TargetColor</code> parameter (or, equivalently, invoke <code>is_point_occupied/2</code>):</p>
  
  <pre><code>is_point_occupied(Board, Point) -&gt;
      is_point_occupied(Board, Point, '_').
  
  is_point_occupied(Board, Point, '_') -&gt;
      ensure_is_point(Point),
      case maps:find(Point, Board) of
          {ok, _} -&gt;
              true;
          error -&gt;
              false
      end;
  
  is_point_occupied(Board, Point, TargetColor) -&gt;
      ensure_is_point(Point),
      ensure_is_color(TargetColor),
      case maps:find(Point, Board) of
          {ok, {TargetColor, _}} -&gt;
              true;
          {ok, _} -&gt;
              false;
          error -&gt;
              false
      end.
  </code></pre>
  
  <p>I don't like the above implementation because of the large proportion of copy-and-paste, so I simplified the above function like this:</p>
  
  <pre><code>is_point_occupied_2(Board, Point) -&gt;
      is_point_occupied_2(Board, Point, '_').
  
  is_point_occupied_2(Board, Point, TargetColor) -&gt;
      ensure_is_point(Point),
      ensure_is_color_or_wildcard(TargetColor),
      case maps:find(Point, Board) of
          {ok, {TargetColor, _}} -&gt;
              true;
          {ok, _} -&gt;
              is_wildcard(TargetColor);
          error -&gt;
              false
      end.
  </code></pre>
  
  <p>The function <code>is_wildcard/1</code> is simply a one-liner:</p>
  
  <pre><code>is_wildcard(Wildcard) -&gt; Wildcard =:= '_'.
  </code></pre>
  
  <p>Now, I would like to go further to replace <code>TargetColor</code> by <code>TargetPiece</code>, which is a <code>{TargetColor, TargetType}</code> tuple. None, one or both of the tuple elements may be a wildcard (<code>'_'</code>). I found it difficult to write the <code>case</code> clauses. I also notice that to match an n-tuple allowing "don't care" in this way, there needs 2<sup>n</sup> clauses. So obviously it is not the proper way to implement this.</p>
  
  <p>Does anyone have better ideas?</p>
  
  <p>PS: I didn't include the source of all functions since those I didn't include are trivial to implement in my opinion. Please leave a comment below if you're interested. Thanks!</p>