• person rss_feed

    Michał "phoe" Herda’s feed

    Blog

    • chevron_right

      WITH_PROCESSES - utility macro for Lisp Flavored Erlang

      Michał "phoe" Herda · Sunday, 23 December, 2018 - 11:56 · 1 minute

    (This is a repost of an old blog post of mine from Teknik.)

    #lfe #erlang

    While working on the LFE Swank Server, I noticed that Erlang's architecture rewards creating multiple tiny specialized processes. Because of this, I will want to have a simple test framework in LFE that leverages this architecture and is able to create "mock" processes. Essentially, what I need is a macro that is capable of spawning processes, binding their PIDs to variables, and cleaning them up after the test is over.

    A WITH-PROCESSES macro.

    But, since the land of Erlang prefers underscores, I instead named it WITH_PROCESSES.

    Writing macros in LFE is a really weird feeling to me, at least when compared to Common Lisp. I have no destructuring lambda lists which I am used to, but I have pattern matching which is slightly more powerful than CL's destructuring. I also got to know Erlang's/LFE's try/catch/after form - from a Lisper's perspective, it's CL:HANDLER-CASE and CL:UNWIND-PROTECT combined.

    See the code for the macro and a very simple test case below. To make sure that the processes actually die, substitute the echo function with a call to (timer:sleep 100000), and then call the (i) function inside and just outside the WITH_PROCESSES body to verify there are timer:sleep calls in the process list inside the macro body, but none of them remain after it is finished.

    (defmacro with_processes
      (`(() . ,body)
       `(progn ,@body))
      (`(((,symbol ,function) . ,rest) . ,body)
       `(let ((,symbol (spawn ,function)))
          (try (with-processes ,rest ,@body)
            (after (erlang:exit ,symbol 'kill))))))
    
    (defun test_with_processes ()
      (flet ((echo () (receive (`#(,pid ,x) (! pid x)))))
        (with-processes 
         ((foo #'echo/0)
          (bar #'echo/0))
         (! foo `#(,(self) 2))
         (receive (2 'ok)
                  (after 1000 (erlang:error "test failed")))
         (! bar `#(,(self) 10))
         (receive (10 'ok)
                  (after 1000 (erlang:error "test failed"))))))