Matching strings in LFE patterns

Eric Bailey

Written on 28 December, 2015
Updated on 18 December, 2023
Tags: lfe, lisp, beam, pattern-matching, open-source

While writing an LFE solution for Day 6 of Advent of Code, I found myself wanting to write parse_instruction/1 like this:

parse_instruction("toggle " ++ Rest) -> toggle(parse_coordinates(Rest));
parse_instruction("turn on " ++ Rest) -> turn_on(parse_coordinates(Rest));
parse_instruction("turn off " ++ Rest) -> turn_off(parse_coordinates(Rest)).

parse_coordinates(String) ->
  {ok,[X0,Y0,X1,Y2],[]} = io_lib:fread("~d,~d through ~d,~d", String),
  {{X0,Y0},{X1,Y1}}.

toggle({{X0,Y0},{X1,Y1}}) -> undefined.
turn_on({{X0,Y0},{X1,Y1}}) -> undefined.
turn_off({{X0,Y0},{X1,Y1}}) -> undefined.

Trying it out

But the literal LFE translation doesn't work as desired.

(defun parse-instruction
  ([(++ "turn off " rest)]
   ...))

N.B. In v0.10.1, exp_append/1 had the following clause, commented out.
%% Cases with lists of numbers (strings).
[[N|Ns]|Es] when is_number(N) -> [cons,N,['++',Ns|Es]];

Instead, invocation of a defun of that form throws a function_clause error.

> (defun f ([(++ "prefix" suffix)] suffix))
f
> (f "prefixsuffix")
exception error: function_clause

Adding ++* patterns to LFE

After this discovery, I took to #erlang-lisp and tried to figure out why. Discussing the issue with @rvirding for a few minutes, we decided adding ++* and having patterns like (++* "prefix" suffix) expand to nested cons-es was a solid approach.

Rather than take the overly complicated approach of counting and limiting the number of expanded cons-es and bottoming out to a call to erlang:++, we decided to keep it simple and just let ++* patterns do their own thing.

The solution we came up with is as follows:

%% exp_predef(...) -> ...;
exp_predef(['++*'|Abody], _, St) ->
  Exp = exp_prefix(Abody),
  {yes,Exp,St};
%% exp_predef(...) -> ....

exp_prefix([[N|Ns]|Es]) when is_number(N) -> [cons,N,['++*',Ns|Es]];
exp_prefix([[]|Es]) -> ['++*'|Es];
exp_prefix(Args) -> exp_append(Args).

Usage

Now in the develop branch, you can do the following:

> (defun f ([(++* "prefix" suffix)] suffix))
f
> (f "prefixsuffix")
"suffix"

or even:

> (defun f ([(++* "p" "r" "e" "f" "i" "x" suffix)] suffix))
f
> (f "prefixsuffix")
"suffix"