PROGRAMMING-WITH-STATE

This documentation section introduces some common techniques for
programming using the ACL2 state object. A prerequisite is thus a basic
understanding of that object; see state. We hope this section is useful,
and we invite suggestions for improvements and additions.

A supplement to this section is the ACL2 source code, which uses most (and
probably all) of the techniques discussed here. That code is thus a source
of many examples, which can serve as ``templates'' to guide one's own
programming with state.

Recall that ``ACL2'' stands for ``A Computational Logic for Applicative
Common Lisp''. In particular, the language is applicative: there are no
global variables or side effects. For many purposes this does not feel
restrictive; for example, an ACL2 user who is programming in raw Lisp may
well be more comfortable coding a factorial function applicatively, using
recursion, rather than using iteration with repeated assignment to the same
variable.

However, there are situations that call for reading or modifying the system
state, such as performing input and output, signalling errors, saving
information from one computation for use in a later one, or reading and
updating system-level or environmental data. This section provides an
introductory guide for writing functions that traffic in state. We emphasize
that this guide is intended as an introduction; more complete documentation
may often be found by following links to documentation of individual
utilities, and again, more examples may be found by searching the ACL2 source
code for uses of the functions and macros mentioned below. The rest of this
section is organized as follows.

ENABLING PROGRAMMING WITH STATE
STATE GLOBALS AND THE ACL2 LOGICAL WORLD
A REMARK ON GUARDS
ERRORS AND ERROR TRIPLES
SEQUENTIAL PROGRAMMING
BINDING VARIABLES USING ERROR TRIPLES
BINDING STATE GLOBAL VARIABLES
INPUT AND OUTPUT
TIMINGS
ENVIRONMENT AND SYSTEM
REMARKS ON EVENTS AND LD
ADVANCED TOPICS

ENABLING PROGRAMMING WITH STATE

In order to submit a definition that takes state as a formal parameter,
you must either declare state as a :stobj (see xargs) or first
evaluate the following form at the top level: (set-state-ok t).

Consider for example the following trivial definition.

(defun foo (state)
(mv 3 state))

If you submit the above definition in a fresh ACL2 session, you will get this
error message.

ACL2 Error in ( DEFUN FOO ...): The variable symbol STATE should not
be used as a formal parameter of a defined function unless you are
aware of its unusual status and the restrictions enforced on its use.
See :DOC set-state-ok.

If first you evaluate (set-state-ok t), you can admit the above
definition. Alternatively, you can declare state as a :stobj,
as follows.

(defun foo (state)
(declare (xargs :stobjs state))
(mv 3 state))

A difference in the two approaches is that for the latter, a guard proof
obligation is generated by default. See the section below entitled ``A
remark on guards''.

STATE GLOBALS AND THE ACL2 LOGICAL WORLD

Recall (see state) that one of the fields of the ACL2 state object is the
global-table, which logically is an alist associating symbols, known as
``state globals'' or ``state global variables'', with values. But no such
alist actually exists in the implementation. Instead, ACL2 provides
utilities for reading state globals -- see @ and see f-get-global --
and utilities for writing them -- see assign and see f-put-global. The
following log shows how they work; further explanation follows below.

Note that the first result is indented by one space. This is ACL2's way to
indicate that the assign expression returned an ``error triple'' and
that no error was signalled. We discuss error triples in more detail below;
also see error-triples.

As illustrated above, the output signatures of the utilities for assigning to
state globals differ from each other as follows: f-put-global returns
state, but assign returns an error triple (mv nil val state)
where val is the value assigned to the state global. The output
signatures of the utilities for reading, @ and f-get-global, are
identical. In fact, the form (f-get-global 'my-var state) is the
single-step macroexpansion of the form (@ my-var), as can be confirmed
using trans1.

ACL2 !>:trans1 (@ my-var)
(F-GET-GLOBAL 'MY-VAR STATE)
ACL2 !>

State globals are useful for conveying persistent state information.
Consider for example the utility set-inhibit-output-lst. The form
(set-inhibit-output-lst '(prove proof-tree)) is approximately equivalent
to (assign inhibit-output-lst '(prove proof-tree)). We say ``approximately''
because set-inhibit-output-lst additionally does some error checking to
insure that all the tokens in the new list are legal. When deciding whether
to print output, the ACL2 system reads the value of state global variable
inhibit-output-lst.

A particularly useful state global is current-acl2-world, whose value is
the ACL2 logical world. Because the ACL2 world is commonly accessed in
applications that use the ACL2 state, ACL2 provides a function that returns
the world: (w state) = (f-get-global 'current-acl2-world state). While
it is common to read the world, only functions set-w and set-w! are
available to write the world, but these are untouchable and these should
generally be avoided except by system implementors (pl[remove-untouchable]).

A REMARK ON GUARDS

For a function definition (see defun), if state is specified as a
stobj as with the form (declare (xargs :stobjs state)), then the
guard for that function is considered to include the condition
(state-p state). By default, guard verification will then be
performed.

We can illustrate this point by modifying the example above as follows, to
read the value of state global gag-mode.

Also see guard-miscellany for a discussion of how guards are generated from
xargs fields of declare forms, specifically, for keywords
:guard and :stobjs.

ERRORS AND ERROR TRIPLES

When evaluation returns three values, where the first two are ordinary
objects and the third is the ACL2 state, the result may be called an ``error
triple''. (Whether it is treated as an error triple depends on the
programmer.) Error triples are often denoted (mv erp val state), and
common ACL2 programming idioms treat erp as a flag indicating whether an
error is being signalled and val as the ``value'' computed. Also
see error-triples.

Even ACL2 users who are not programmers encounter error triples, because
these are the values returned by evaluation of ACL2 events. Consider
the following log, where the only user input is the defun form
following the prompt.

All output above results from explicit calls of output functions, except for
the next-to-last line, which contains FOO. Notice the single-space
indentation preceding FOO. That space indicates that in fact, the value
returned by evaluation of the defun form is the error triple whose error
flag is nil and whose computed value is FOO. By default, ACL2 prints
any error triple (mv nil val state) by inserting a space before printing
val. You can change the default by setting state global
ld-post-eval-print to t; notice how the same result is printed
below.

The way error triples are printed by ld is controlled not only by state
global ld-post-eval-print, but also by state global ld-error-triples.
These are examples of ``ld specials''; see ld, see ld-post-eval-print, and
see ld-error-triples.

It is so common to produce an error triple whose first (error flag) component
is nil that ACL2 provides a handy macro, value, for this purpose.
Thus, (value <expression>) is equivalent to
(mv nil <expression> state). Also see value-triple for a similar
construct that is a legal event form.

We turn now to the topic of errors. The macro ER ``causes'' an error,
but there are really two quite different kinds of errors: ``soft'' and
``hard'' errors. We use the term ``soft error'' to refer to a form that
returns an error triple (mv erp val state) for which erp is
non-nil. Soft errors do not interrupt the normal flow of evaluation: the
error triple is returned to the caller which interprets the erp flag and
val as directed by the programmer. Macros discussed below make it
convenient to think about soft errors as short-circuiting the computation.
Hard errors, on the other hand, do actually rip control away from the current
evaluation and return it to the top-level loop. Logically speaking,
expressions that cause hard errors return nil in the error case, but the
nil is never seen in actual evaluation because control does not return to
the caller.

Note that the function abort!, which you can invoke by typing
:a!, always returns to the top level. Note that ACL2 can
prove that (abort!) returns nil but that this cannot be confirmed
by computation.

(What actually happens with a hard error, including non-default cases, is a
bit subtle; most readers will probably want to skip this paragraph. The
read-eval-print loop implemented by ld is implemented by a call of the
ACL2 evaluator function, trans-eval, on each input form. If a hard
error occurs during evaluation of an input form, its trans-eval call will
return with a soft error. Ld, in turn handles that soft error
appropriately; see ld-error-action.)

The most common way to signal errors is the macro er, which prints a
formatted error message and returns a soft or hard error as specified by the
call. Note however that soft errors are signalled using :program
mode functions.

Since the output signatures of soft and hard errors are different -- hard
errors ``return'' a single value while soft errors return a triple --
mixing them in an expression requires embedding the hard error form in (an
irrelevant) triple, as illustrated below. All branches of the expression
must produce an error triple if any branch does.

In the next section we discuss soft errors further, in the context of
programming.

SEQUENTIAL PROGRAMMING

This section describes handy ways to modify state in steps, using macros that
implement a sequence of let or mv-let bindings. For example,
suppose you want to assign the values 1 and 2 to two state globals
one-var and two-var, respectively. Because of ACL2's syntactic
restrictions on state, it is not legal simply to write
(f-put-global 'two-var 2 (f-put-global 'one-var 1 state)). However,
let comes to the rescue as follows.

See pprogn. Note that the last form is allowed to return multiple values;
the only requirement on the last form is that its value include state.

It is also common to update the state using a sequence of forms such that
each returns an error triple, where the intention is for evaluation to
short-circuit immediately if a soft error is encountered. Suppose
<expr1> and <expr2> are expressions that return error triples, where
the state components of the error triples might be updated, and one
wishes to evaluate <expr1> and then <expr2>, returning the (multiple)
values returned by <expr2> unless the error triple returned by
<expr1> is a soft error, in which case that error triple is returned.
One can of course do so as follows.

But ACL2 provides a handy macro, er-progn, for this purpose. The
following code is equivalent to the code just above.

(er-progn <expr1> <expr2>)

See er-progn for more details. Note that unlike pprogn, the return
signature for the last expression must be the same as that of the
others: an error triple.

Let's consider how to use pprogn and er-progn together. In the
following example f1 and f2 both return state, while each of
g1 and g2 returns an error triple. The following code modifies state
by executing these in the order f1, g1, f2, and finally g2,
returning (mv nil val state) where val is the value component of the
error triple returned by g2 -- except we return a soft error if g1
or g2 returns a soft error.

Finally, consider the eventsprogn and progn!. These have
similar behavior to that of er-progn. However, progn and
progn! may only be used in event contexts, for example at the top level
or immediately underneath a call of encapsulate or progn, while
er-progn has no such restriction. So when writing code, use
er-progn rather than progn or progn!. In particular, the
body of a defun must not have any calls of progn (or of progn!
either), and the same restriction holds for any code to be executed, such as
the body of a make-event form.

BINDING VARIABLES USING ERROR TRIPLES

In this section we discuss the macro er-let*, which is a variant of the
special form, let*, that is useful when programming with state.

The macro er-let* is useful when binding variables to the value
components of error triples. It is actually quite similar to er-progn,
described above, except that er-let* binds variables. First consider the
following example.

(er-let* ((x1 (f1 state))
(x2 (f2 x1 state)))
(value (cons x1 x2)))

The code just above is essentially equivalent to writing the following.

As suggested by the example above, er-let* has the same syntax as
let*, except that declarations are not supported. (But note that
ignore declarations are not needed; all variables that are bound are also
used, at least in the error case. Consider replacing (cons x1 x2) by
nil in the example displayed immediately above, and note that x1 and
x2 are still used.) However, unlike let*, er-let* requires that
for each binding (var expr), the expression expr must evaluate to an
error triple and, moreover, it requires that the second argument (the
``body'') of er-let* must evaluate to an error triple. If one of the
variable expressions (e.g., the f1 and f2 calls above) signals an
error, its error triple is returned as the value of the er-let*.

Of course, soft errors can be ``caught'' by using mv-let instead of
er-let* and simply ignoring the error flag or, more generally, by
returning a non-erroneous error triple even if the error flag was on.

BINDING STATE GLOBAL VARIABLES

In this section we introduce a utility, state-global-let*, that is an
analogue of let* for state global variables. Consider the following
example.

This form binds state global variable inhibit-output-lst to the result of
adding the symbol, summary, to the current value of that state global.
Thus (see set-inhibit-output-lst), the usual summary is not printed when
evaluating this call of thm.

In general, undefined or surprising behavior may occur when using ACL2
events or calling ld in your programs. In some cases ACL2 enforces
restrictions against these uses. We strongly discourage using ld in
programs, as it has been designed to be called only at the top level of a
read-eval-print loop.

There is also a restriction on contexts in which make-event may be
called: it may only be called in a context where an event is expected, such
as the top level, in a book, or as an argument of encapsulate or
progn. The reason is that ACL2 does very subtle and careful tracking
of make-event expansions; and it is only able to do this in event
contexts, where it is able to carry out such tracking accurately.

ADVANCED TOPICS

ACL2 provides the function trans-eval to evaluate an arbitrary form
(after translating it to a term, i.e., into internal form). For more
information, we refer to reader to comments in the definition of
trans-eval in the ACL2 source code. There are also many examples of its
use in the ACL2 sources.

For a function that provides the true absolute filename, with soft links
resolved, see canonical-pathname.

For a function that returns a check-sum on the characters in a channel,
see check-sum.