Tutorial: Counter Application

4 Thinking About Lisp

Help from Closures

Now the example we've created is nearly a one for one replication of the original Erlang version from the blog post. How about we starting playing with some more 'Lispy' functionality to see if we can't spice things up a bit. Disclaimer: These may not necessarily be improvements, more to demonstrate that simply writing Erlang code in Lisp isn't enough, there is so much more than can be done!

In our original version we spawn the process and return the pid in the usual manner, this is all well and good but then every time the user wants to increment the counter they have to make sure the pid is available and then call one of our functions and pass the pidback in.

Some Refactoring

Is there an easier way to manage that? Funny you should ask!

(defun incrementer (pid)
  (lambda ()
    (! (tuple 'incr))))

(defun new ()
  (let ((pid (spawn 'counter 'counter '())))
    (tuple (incrementer pid)))))

Here we've created an incrementer function that returns a button of sorts that the user can just hit and it will increment the counter process. We've also adjusted the new function so that we return that instead of the pid. We can use it like so:

> (c '"counter.lfe")
#(module counter)
> (set (tuple pid foo) (: counter incrementer))
#(<0.45.0> #Fun<counter.0.27204963>)
> (funcall foo)
#(incr)
> (funcall foo)
#(incr)
> (: counter count pid)
2

Isn't that nice? We can just hit the button and be confident that the correct counter is being updated.

More Refactoring

But what can we do about still needing the pid to ask for the count? We can use a similar solution:

(defun count-requestor (pid)
  (lambda ()
    (tuple (: counter count pid)
           (count-requestor pid))))

This time, because we want the actual incremented value we have to return two things, the current value of the counter, and another copy of the count-requestor function.

Now when we call this function we'll receive a tuple which has value of the counter, and copy of the function so it can be called again later. With just a little adjustment to our new function we don't have to worry about keeping track of pids:

(defun new ()
  (let ((pid (spawn 'counter 'counter '())))
    (tuple (incrementer pid)
           (count-requestor pid)))))

We can then use it in the shell like so:

> (c '"counter.lfe")
#(module counter)
> (set (tuple i c) (: counter new))
#(#Fun<counter.0.3300826> #Fun<counter.1.3300826>)
> (funcall i)
#(incr)
> (funcall i)
#(incr)
> (set (tuple result new-c) (funcall c))
#(2 #Fun<counter.1.3300826>)
> result
2
> new-c
#Fun<counter.1.3300826>

The Completed Module

So now our final version should look something like this:

(defmodule counter
  (export (new 0)
          (count 1)
          (counter 0)))

(defun new ()
  "For creating a new counter."
  (let ((pid (spawn 'counter 'counter '())))
    (tuple (incrementer pid)
           (count-requestor pid)))))

(defun incrementer (pid)
  "Increments our counter value."
  (lambda ()
    (! pid (tuple 'incr))))

(defun count-requestor (pid)
  (lambda ()
    (tuple (: counter count pid)
           (count-requestor pid))))

(defun count (counter)
  (! counter (tuple 'count (self)))
  (receive
    ((tuple 'count count)
     count)))

(defun counter ()
  "Retrieves the value of the given counter."
  (counter-loop 0))

(defun counter-loop (count)
  (receive
    ((tuple 'incr)
     (counter-loop (+ count 1)))
    ((tuple 'count requestor)
     (! requestor (tuple 'count count))
     (counter-loop count))
    (msg
     (: io format '"Unknown message received ~p~n" (list msg))
     (counter-loop count))))

Check out some of our other tutorials and the documentation for more awesome ideas on how Lisp can really some spice to your Erlang.