Yesterday I gave an example of the operator `try'.
Today, you'll see how `retry' nicely complements
it.
The previous code tried to increment one of two
counters. It didn't matter which one we incremented
as long as we incremented! Lets generalize from
that example to increment any counter in a list of
counters:
(deftransaction increment-any (counters)
(tif (trans (null counters))
(retry)
(try (increment (car counters))
(increment-any (cdr counters)))))
Here `tif' is a macro just like `if', but it takes transactions
as its test and branches. That's why the (null counters)
is wrapped up in a `trans'. As you can infer, that means
the return value of `retry' and `try' are transactions.
Looking at the second clause tells us that this is a
standard car/cdr recursion. We try to increment the
first counter, and if we fail, we try the rest of them.
The base case is where we call retry. So if the list
is empty, the transaction is *aborted* and starts
from scratch again. The transaction waits on all
the variables that have been read until `retry' was
called. This guarantees that the transaction will
eventually commit. Here is an example:
STM> (progn
(defvar *c1* (new 'counter))
(defvar *c2* (new 'counter)))
*C2*
STM> (acquire-lock (lock-of (count-of *c1*)))
T
STM> (acquire-lock (lock-of (count-of *c2*)))
T
STM> (perform (increment-any (list *c1* *c2*)))
[debugging output elided...]
STM> (values (value-of (count-of *c1*)) (value-of (count-of *c2*)))
0
0
STM> (release-lock (lock-of (count-of *c1*)))
NIL
STM> (unwait (count-of *c1*))
[debugging output elided...]
STM> (values (value-of (count-of *c1*)) (value-of (count-of *c2*)))
1
0
So that was just the same as yesterday. But we can
do better. `increment-any' is just a special case of
trying any transaction down a list, just like `map' is
a special case of apply a function down a list. So
the equivalent would be:
(deftransaction try-list (transaction list)
(tif (trans (null list))
(retry)
(try (funcall transaction (car list))
(try-list transaction (cdr list)))))
And we could now define `increment-any' as:
(deftransaction increment-any (counters)
(try-list #'increment counters))
Now running it gives us:
STM> (perform (increment-any (list *c1* *c2*)))
[debugging output elided...]
STM> (values (value-of (count-of *c1*)) (value-of (count-of *c2*)))
2
0
And indeed it works the same as before. Indeed
we could even use `try-list' in many other situations,
like reading from a socket like Unix's select.
Now I am in a bit of a conundrum with regards to
the interface that CL-STM should have. At the
moment I'm not happy with using all these new
macros like `progt', `progt1', `tif' and so on because
it means the programmer has to learn all these new
names that are just variations of the standard CL ones.
Its also a pain to figure out where to use `trans' and
`untrans'.
I've started some work on a code walker that transforms
CL forms into STM ones. I have the equivalents of `tlet',
`progt' and `tif' implemented. Even better is the code
walker handles macro expansion, so that `cond', and
`prog1' will be taken care of. BTW, this code walker
will only traverse forms inside a `deftransaction'.
Ideally we'd like to write code without explicit `trans'
and `untrans'. Even in the simple `increment-any'
example, we had to use `trans'. It would be nice
if we could tell in advance whether or not a particular
expression is a transaction or not. But Common Lisp
doesn't have a type system. But here is what we know:
Forms like `(trans ...)' are transactions.
A form is a transaction if its car is a symbol defined with
`deftransaction'. So (increment-any ...) is a transaction.
Assuming all transactions are take the form above,
we can infer that anything else is not a transaction.
So (null ...) is not a transaction, and thus the walker
can wrap it with `trans'.
The above analysis doesn't work for higher-order
functions. It assumes incorrectly that (reduce ...)
is doesn't return a transaction. But it could return
a transaction depending on the function passed
to it. So the analysis only work on first-order
functions.
I'm wondering if this is the best we can do? I've
still got more thoughts on this but now I'm asking
you. What interface would you like? Do you mind
writing `trans' and `untrans'? Would you accept
a half-solution? Whatever, just tell me whats on
your mind. Did you have a good day? Are you
following the world cup?
Hoan