Local Higher-Order Fixpoint Iteration

Florian Bruse School of Electrical Engineering and Computer Science
University of Kassel, GermanyInstitute for Computer Science
University of Applied Sciences Fulda, GermanySchool of Electrical Engineering and Computer Science
University of Kassel, Germany
   Jörg Kreiker Institute for Computer Science
University of Kassel, Germany
   Martin Lange   Marco Sälzer School of Electrical Engineering and Computer Science
University of Kassel, Germany

Local fixpoint iteration describes a technique that restricts fixpoint iteration in function spaces to needed arguments only. It has been studied well for first-order functions in abstract interpretation and also in model checking. Here we consider the problem for least and greatest fixpoints of arbitrary type order. We define an abstract algebra of simply typed higher-order functions with fixpoints that can express fixpoint evaluation problems as they occur routinely in various applications, including program verification. We present an algorithm that realises local fixpoint iteration for such higher-order fixpoints, prove its correctness and study its optimisation potential in the context of several applications.

1 Introduction

Fixpoints are ubiquitous in computer science. They serve to explain the meaning of recursion in programming languages [36], database queries [2], formal languages and automata theory [32]; they are being used as logical quantifiers in descriptive complexity [20] or as specialised operators, for instance in temporal logics [16], etc.

Fixpoints often link a denotational and an algorithmic view onto computational problems, most specifically through Kleene’s Fixpoint Iteration Theorem [22]: start with the least, resp. greatest value of the underlying lattice, and then keep applying the function under consideration until the sequence becomes stable. This theorem provides the algorithmic foundations for many applications in which fixpoints play an important role. For instance in model checking, fixpoint operators are used to describe correctness properties [18], and methods based on fixpoint iteration are being used to establish the satisfaction of such properties by models of programs [6]. Fixpoints are used in programming language semantics to explain the meaning of recursive programs. This extends to static analysis methods. For instance in the original formulation of abstract interpretation [14], collecting semantics extends program semantics to powersets of semantic values ordered by subset inclusion [15]. Computing program properties then amounts to solving fixpoint equations over a number of specific (powerset) domains. Fixpoint iteration also provides a standard means for the evaluation of recursive database queries [2].

In many applications, the elements of the lattices in which fixpoints are being sought, are functions themselves. In strictness analyses for functional languages [28, 12] for instance, properties under consideration are sets of functions. Denotational semantics is perhaps the application domain which is most easily seen to need lattices of functions, possibly of higher order, in order to explain the meaning of, for example, functional programs of higher order. Certain infinite-state model checking problems, in particular so-called higher-order model checking [30] are tightly linked to the evaluation of fixpoints in functions spaces as well [23].

We are concerned with the problem of finding fixpoints in such a lattice of functions of some higher order. Kleene fixpoint iteration in its pure form can still be employed here, but in many situations it is naïve and inefficient for the following reason. Suppose one is not interested in the entire fixpoint (which is a function of some type ) but only in the value of this on some particular argument . Naïve fixpoint iteration would start by approximating this function with the least one that maps anything to the least element of the lattice , and successively compute better approximations until for some . Then it would return which equals .

This procedure has then also computed values for any . It has been observed that a more efficient approach would be goal-driven and avoid the computation of on any unnecessary argument. Note that, since is defined recursively, the value may depend on some but not all values for . The term neededness analysis was coined to describe the goal-driven evaluation of fixpoints in function lattices, avoiding the computation of function values on arguments that do not contribute to the computation of the one value of interest.

Neededness analysis has been studied well for lattices of first-order functions as they often arise in abstract interpretation [21]. In groundness analyses for logical programs such as [13], instead of neededness, one rather speaks of a fixpoint computations being local [19], when a solver tries to only compute the values of as few variables as possible. Neededness analysis has also been studied in the context of model checking complex program properties which cannot be described in the standard temporal logics of regular expressivity (CTL, -calculus) but in extensions using predicate transformers [3]. This can be seen as a notion of on-the-fly model checking for fixpoints of order . Since “local” is also a synonym for “on-the-fly” in model checking [9], we stick to the term local fixpoint iteration here rather than the more cumbersome neededness analysis, when referring to a method to avoid the computation of all arguments of fixpoints which are functions themselves.

In this paper we consider the applicability of local fixpoint iteration in function lattices to arbitrary higher orders. To this end, we define a simple abstract and typed higher-order fixpoint algebra in Sect.  2 which can be used to describe evaluation problems involving fixpoints in such lattices. We then give a generic local algorithm for evaluating fixpoint terms in higher-order lattices in Sect. 3. It optimises the naïve fixpoint iteration method by localising the evaluation of recursively defined functions at the top order. A formal proof of its correctness is omitted due to space constraints. In Sect. 4 we present some computation problems which are special instances of the evaluation of higher-order fixpoints in various domains and discuss local evaluation’s optimisation potential by comparing numbers of iteration, resp. argument computation steps on some hand-crafted examples. In Sect. 5 we briefly sketch limitations to the local approach of fixpoint iteration for higher-order fixpoints, in the form of obstacles to overcome which do not exist in the first-order case. We conclude in Sect. 6 with an outlook onto further work in this area.

2 An Abstract Higher-Order Fixpoint Algebra

Types and higher-order lattices.

Let be some base type. Types are derived from the grammar

where the annotations are called variances, and they specify the dependency of the values of a function of type on their arguments. In particular, if , then this dependency is monotonic, if then it is antitonic, and it then it is unspecified.

The order of a type is and

As usual, a function on a partially ordered set is monotonic if for all with we have . It is antitonic if for all such we have . A lattice is a partial order in which suprema and infima, denoted , , resp.  and for any exist, for as long as is finite. A lattice is complete if these also exist for arbitrary . Complete lattices always contain a least and a greatest element, usually denoted and here. A finite lattice is trivially complete.

Let and for some be complete lattices. Remember the following constructions on lattices:

Inverse: where iff . For notational convenience we also let . It should be clear that these two operations not only preserve the property of being a lattice but also completeness.

Flattening: , where denotes equality as usual. Note that is in general not a lattice anymore, let alone a complete one.

Product: where iff for all . The product lattice is complete if all its components are complete.

Higher-order: is monotonic where if for all . The lattice of componentwise ordered monotonic functions from to is complete if is complete. Completeness of is not required, not even the property of being a lattice since is not used in the definition of .

We can use these constructions to associate, with each type , a complete lattice , given a complete lattice interpreting the ground type :

Note that each is indeed a complete lattice given the remarks above, as the flattening operation that breaks the lattice property is only used on the argument side of the function operator. Moreover, if is finite, then so is for all .

Abstract higher-order fixpoint algebra.

Let be a complete lattice and be a set of computable and typed functions on , possibly of higher-order. Note that if , then is not really a function but rather a constant. For simplicity we speak of functions in this case as well.

Let be a set of typed variables. We write , resp.  for the uniquely determined type of variable , resp. function . We will also simply write instead of and likewise for the members of .

Terms of the abstract higher-order fixpoint algebra over , or simply when is clear from the context, are built via

where , and .

A term is closed if it contains no free variables, where an occurrence of a variable is free if it is not under the scope of some or or in the syntax tree of . In the following, we are mainly interested in closed terms; others will usually only occur as subterms of these. Hence, we will often simply speak of terms when in fact we mean closed terms at syntactic top-level.

We assume terms to be well-named, i.e. each variable is bound at most once. Clearly, any term can always be made well-named by renaming bound variables.

For better readability, we simply write instead of , for .


Γ⊢f:τf \inferruleΓ⊢φ:τ1^v1 ×⋯×τn^vn →τ
Γ^v1 ⊢φ1:τ1

Γ^vn ⊢φn:τnΓ⊢φ(φ1,…,φn) :τ \inferrulev ∈{+,±}{ x^v, …} ⊢x:τx \inferruleΓ[xi^vi ∣i=1,…,n] ⊢φ:τΓ⊢λx1^v1,…,xn^vn. φ:τx1^v1×⋯×τxn^vn →τ \inferruleΓ[x^+] ⊢φ:τxΓ⊢σx. φ:τx

Figure 1: The typing rules for abstract higher-order fixpoint algebra.

In order to give terms a well-defined semantics via the Knaster-Tarski Theorem, each in a term or needs to denote a function that is monotone in its argument . Monotonicity is guaranteed for well-typed terms, to be explained next, and then formally stated as Lemma 2.1 below. Note that the variances are used to track information about the monotonicity or antitonicity of functions in particular arguments, and that a monotonic function can be built for instance by composing two antitonic ones.

A typing statement is a triple where is a term, is a type, and is a typing context consisting of typing hypotheses of the form for and being a variance. For a typing context , let ; let result from by replacing in it every by and vice-versa; and let , i.e. the context which only contains typing hypotheses of the form from . The typing context is obtained by removing from for any , and adding instead.

A term has type if the typing statement is derivable using the typing rules given in Fig. 1. The rules are standard; they state, for instance, that in function application , must have a function type with arguments which are the types of the respective argument terms. Moreover, the arguments themselves have to be typbale in the respective derived typing contexts. For example, if is antitonic in its first argument, then has to be typable in the typing context , where is the context used to type the whole application. This reflects the fact that an antitonic function from some lattice is a monotonic function from the inverse of this lattice (cf. the lattice definitions above and the definition of the semantics below). The rules for fixpoint formulas require the term to be of the same type as , since being a fixpoint intuitively means , and at the same time ensure that is monotonic in . A term is well-typed, if it is of some type.

Variance annotations are only used to guarantee well-typedness (and therefore the existence of fixpoints). We will always assume that terms are well-typed, and therefore often drop typing annotations for better readability. Note that for closed terms, a unique type for each subterm can easily be recovered.

The semantics of terms.

Let be a complete lattice, and suppose that all base functions have an interpretation in the family of higher-order lattices over according to their types. A term of over and a set of typed variables gets interpreted in this family of lattices. In order to explain the value inductively, we need variable interpretations which assign values in lattices over to any variable with free occurrences in subterms: for each we have . The value of over and under is denoted and is defined inductively as follows.

The fourth clause, in particular its right-hand side, denotes the function that maps a tuple of objects from to the value where the subscript index denotes the variable environment that results from by replacing its bindings for accordingly. For the last two clauses, note that the values on the right-hand side are well-defined according to the Knaster-Tarski Theorem [33] since each is a complete lattice. Note that the semantics of are easily seen to be invariant under -reduction.

Lemma 2.1.

Let be a lattice, be a term of type under the typing assumptions . If , then is monotone in ; if then is antitone in .


By a straightforward induction on the syntax tree of . ∎

Remark 2.2.

Over finite lattices, each of the type lattices is finite as well. According to Kleene’s Fixpoint Theorem, the least and greatest fixpoints of a term in under a variable interpretation can be computed by a sequence of approximations as follows: if , if , and . Then, for each finite lattice there is such that . Moreover, these approximations are definable in , independently of : is defined by , and is defined by the substitution instance .

Evaluation problems.

We consider the following generic evaluation problem: given a (closed) term of with symbols in interpreted in the higher-order lattices over a finite , compute .

This problem is clearly decidable when all basic functions in are computable. A naïve algorithm will simply compute the value of each subterm in a bottom-up fashion using Kleene iteration to evaluate fixpoint expressions, and possibly storing function values as tables. Note that if is finite, so is for any , but the size and height of are -fold exponential in the size, resp. height of when .

Even for low orders, such a naïve algorithm may perform far too many unnecessary computation steps. Consider the following special local variant of the evaluation problem: given a finite complete lattice , a closed term of type (which is then necessarily the same as ) for some , and a term of type , compute .

Note how this problem formulation describes a situation in which naïve fixpoint iteration obviously performs too many evaluation steps in general: it computes using Kleene iteration which results in a function of type . Depending on the order of , this function is huge in terms of its arguments but still finite. We would then also compute . Then we obtain the value by application, for instance through a simple look-up in the table representing , where occurs as some argument. Clearly, the value of on all other arguments is irrelevant, and the reason for their computation is questionable.

A (first-order) example.

Consider the Boolean lattice and the normal Boolean functions interpreted in the standard way. Let and

over where, for and ,

  • returns iff encodes the numerical value , for instance ;

  • returns iff encodes an even number, for instance ;

  • and represent the operations “” and “” on bit strings;

  • yields a bit string representing the addition of the two values modulo .

It is not difficult to find Boolean functions realising these operations.

Intuitively, defines a search procedure. Note that any value of encodes a number in the range which we will simply denote as well. For any value , define a sequence via , if is even, and if is odd. Hence, suppose encodes such a number, then is true iff this sequence eventually hits the value .

Let . The graph on the right depicts the sequence on all values in . Assuming

that the Boolean function only evaluates its second argument when the first one is not , this graph suggests how local fixpoint iteration of this first-order function can be more efficient when the value of the fixpoint is only needed on one particular argument. The effect of global fixpoint iteration is depicted in Fig. 2 (left). Here, the iteration starts with the least function , and it terminates when the current approximation equals the last one.

Local fixpoint iteration on the other hand only adds the arguments successively to those tables. Consider the case of evaluating which means iterating the numerical series beginning at . First we only tabulate the approximant on the value under consideration, i.e. . In order to compute we need , so gets added as a new argument and receives the initial value there. Then we compute and , and so on. The iteration stabilises when no change is being recorded anymore, thus computing values as they are shown in the table of Fig. 2 (middle).

The effect of computing , i.e. beginning the numerical series at is similar. Here, however, is never reached. Hence, all values encountered are , and the iteration stabilises when no further arguments are needed, as shown in Fig. 2 (right).

Even though the local iteration computes a value of while the global one only reaches , it should be clear that local evaluation performs fewer computation steps in general.

Figure 2: Global (left) vs. local fixpoint iteration for a first-order function .
Remark 2.3.

It is well-known that fixpoint iteration does not need to record the entire history of its computation but, for each variable, merely the value of the last iteration. In the left table (with only one fixpoint variable) in Fig. 2, this corresponds to two successive rows: the upper for the last approximate value of a function, and the lower for the current value. In the two tables on the right using local iteration, this corresponds to diagonals, but the picture is more complicated in general, for instance for fixpoint terms in which the fixpoint variable has multiple occurrences.

The tables shown in Fig. 2 not only give an idea of how local fixpoint iteration works, their width and height are also good measures for the space, resp. time needed to compute such a higher-order fixpoint.

3 Local Fixpoint Evaluation for Full

The algorithm.

Procedure Eval in Alg. 1 solves the evaluation problem for terms of arbitrary higher order and finite lattices using local fixpoint iteration. It takes four parameters: (1) a term over some . It is not necessarily of type , but the algorithm is assumed to be started with a full list of arguments (see below) in order to realise local fixpoint iteration. (2) A finite, and, hence, complete lattice with an interpretation of any as an object in . (3) A list of arguments to . The following invariant is maintained: if the type111Variances are not important in this section. In order to reduce clutter, we do not display them. of is , then for all . (4) A global variable env that is used to interpret free variables. Values of -bound variables are stored as full functions222This might appear wasteful at first, but -bound variables are never of the highest type (by order) that occurs in the term to be evaluated except in pathological cases, which can be eliminated by -reduction before calling Eval., values of fixpoint variables may be stored as partial approximations as described at the end of the previous section.

In order to bridge the gap between a variable assignment , which assigns a value to each variable which is defined at every argument, and the global variable env which only stores partial approximations for fixpoint-bound variables, consider the following definition. It turns a state of env into a well-defined variable assignment:

Here, is , resp.  for variables that are bound to a least, resp. greatest fixpoint.

Note that, due to the invariants, a state of Alg. 1, i.e. a call of with a value of env and over some higher-order lattice , can be thought of as computing the object , which is always a member of . The algorithm computes this value recursively by descending through the syntax tree of . Fixpoints are resolved by Kleene iteration until the semantics computed stabilises, but the value is only computed at the arguments indicated plus all those arguments that are discovered as necessary to obtain the value for the original argument.

procedure :                                   global (partial)
switch :
case : return
case : if then
case :
case : for do
case :
for all do
Algorithm 1 Neededness-based evaluation for abstract higher-order fixpoint algebra.

We explain the algorithm’s functionality by considering the different cases for its argument . Upon reaching a basic function symbol, Eval simply applies the semantics of this basic function to the arguments in the argument list. When Eval reaches a variable and the value of that variable at argument is defined, then its value is returned. Otherwise, the variable must be fixpoint-bound, and Eval has discovered a new tuple of arguments at which the value of this fixpoint is needed. This value is initialised as , which also registers in env. In this case the initial value is returned.

At a -abstraction, a number of arguments corresponding to the abstracted variables are transferred from the argument list to env, i.e. they are now treated as bound variables. In an application , Eval computes, for each argument, its full semantics by a number of recursive calls to Eval333This can be done lazily, in case the argument is not needed or has been already computed. We omit the details for this in order to keep the presentation simple.. The obtained values (as functions) are then added to the list of arguments.

Upon reaching a fixpoint binder for variable , Eval (re-)sets to the singleton definition that initialises the value of the fixpoint at to the default value of . Then, for each argument tuple that is already discovered as necessary for the value at , the algorithm computes a new value. Note that, during this process Eval can reach the variable case and discover new argument tuples. This procedure of updating the value at all known argument tuples is repeated until both no new arguments are discovered for one round, and the value of the fixpoint at each of the tuples agrees with that of the last round. If this has happened, the value of the last iteration at is returned.


The formal correctness proof for Alg. 1 uses the following lemma which formalises the converse of Lemma 2.1. Take a term that is typed with hypotheses . Not only is it monotone (if ), respectively antitone (if ) in the value of . If the value of also differs genuinely under two variable interpretations that only differ in , then must occur freely in and there are arguments to the value of on which this difference manifests itself. We write to denote that but .

Lemma 3.1.

Let be a finite, and hence, complete lattice, be a variable interpretation, with for some , let be values with for , , and be a term such that . If

then appears freely in , and there are such that

with if and if .


By induction on the structure of . Details are omitted. ∎

Next we state correctness of Alg. 1. It is not hard to imagine local fixpoint iteration to be sound (for least fixpoints, resp. complete for greatest ones) since it clearly only performs part of a global fixpoint iteration that is sound and complete according to Kleene’s Theorem. For completeness one has to see that the value of a fixpoint function on some argument is determined solely by the value of that function on its dependent arguments. Hence, it suffices to iterate on these until stability is reached.

We write for the result of the call of Alg. 1 on with arguments and such that env satisfies .

Theorem 3.2.

Let be a finite, and, hence, complete lattice, be a variable interpretation, be a term of type , let be values with , and env be such that . Then .


The proof is, again, by induction on the structure of . Details are omitted. ∎

A natural question that arises is the one after the time and space complexity of local higher-order fixpoint iteration. Two aspects need to be considered here. First of all, it should be obvious that local evaluation cannot improve the worst-case. It is in fact not hard to construct examples which a fixpoint term of higher-order such that its evaluation causes all argument values to be explored. Consider the (order-1) term with interpreted in the usual way known from modal logic, over the powerset lattice induced by the Kripke structure

Even though the term evaluates to , local fixpoint iteration will successively discover all arguments to the first-order function before termination. It is also possible to extend this example to an arbitrary higher order.

Second, the question after the space and time complexity of Alg. 1 cannot be answered without making assumptions on the representation of the lattice and the complexity of evaluating base functions. So far, no assumptions have been made explicitly, even though it is clear that such functions should at least be computable for otherwise Alg. 1 would not be well-defined. A reasonable assumption is that each base function of order can be evaluated in time and space that is at most -fold exponential in the size of the underlying lattice, with -fold meaning polynomial and -fold meaning logarithmic. Logarithmic bounds may seem highly restrictive at first glance, but they make sense in cases where the underlying lattice is obtained as the powerset lattice of some other structure, see the example above. If this assumption is met, then it is not too hard to see that Alg. 1 runs in time and space that is at most -fold exponential with being the order of the input term. This also assumes that the lattice is given in a logarithmically sized representation. Otherwise, the complexity drops by one exponential.

4 Applications

We present four applications of fixpoint evaluation in higher-order lattices using local fixpoint evaluation, and estimate how many computation steps can be saved compared to a naïve bottom-up and global fixpoint iteration.

Constrained reachability problems.

Reachability problems – to decide whether some node is reachable from another in a directed graph – are ubiquitous in computer science. In some applications, simple reachability is too coarse; instead one wants to put constraints on the form of path under which the target node can be reached from the source, for instance in terms of distance, weight, shape or allowed sequence of edges. The latter can easily be formalised as a reachability problem constrained by some formal language. This has been investigated thoroughly for regular [7] and context-free languages [8, 4, 25] for applications in database theory, model checking, or in static program analysis for heap-manipulating programs [26, 17]. Little has been done for larger classes of languages.

We consider the context-sensitive language over the alphabet , and the problem whether for some given nodes of a directed, edge-labelled graph there is a path from to whose edge labels form a word in . Reachability problems can be interpreted in powerset lattices as they can be seen as least fixpoints of functions from sets of nodes to sets of nodes. However, if is backwards-deterministic, i.e. for all , we have , it is possible to formalise such problems over a smaller lattice.

Figure 3: Directed graph with (left) for the language-constrained reachability problem example and corresponding base lattice (right).

Consider the graph depicted in Fig. 3 on the left. It contains a central state and around this three loops: an -loop with states, a -loop with states and a -loop with states. It is backwards-deterministic. Let be interpreted by the lattice shown in Fig. 3 on the right. Intuitively, can be read as “a path from source to the target has not been found yet”, and signals that such a path has been found.

We use base functions as follows. The constant denotes the state . For any , is the -predecessor of ; likewise for and . The value is if no such predecessor exists, in particular when applied to . The value on is itself. For instance, , , if , and otherwise.

In the powerset lattice , could simply be interpreted as set union. However, here we interpret it as an if-then-else in the following way. Note that it is only monotonic in its second argument.

Now let and consider the term

where .

Using fixpoint unfolding and -reduction one can see that the value of becomes when or or and so on. Hence, evaluating solves the reachability question “is there a path from to under some word in ?”

We analyse how much computation power is being saved when computing the answer to the question of whether there is an -path from to in . We compare four situations arising from the use of the standard powerset lattice vs. the optimised flat lattice of Fig. 3, as well as local vs.  global enumeration of all arguments to higher-order functions.

Note that the three cycles in have lengths , and respectively, which are always co-prime for each . Hence, the shortest -path from to is the one that performs many rounds on the -cycle, then rounds on the -cycle and then rounds on the -cycle.

The following table shows the computational effort needed to evaluate in terms of the number of arguments, resp. width of the table representing the function . It also shows the space that is needed in order to represent one argument, i.e. a triple where are first-order functions and is a lattice element. Finally, in all cases the height of the table, i.e. the number of fixpoint iterations needed until stabilises, is in .

powerset lattice flat lattice
evaluation     global       local     global       local
width of table for
size of arguments
b       c       b          c          c       c

Figure 4: Higher-order grammar (left); example word (middle); lattice (right).

Parsing of programming languages with indentation.

Some programming languages like Haskell or Python use indentation as a syntax element. Such an effect can be described conveniently by the higher-order grammar shown in Fig. 4 (left), over the terminal alphabet (for “block”, “code” and “space”). We refrain from defining higher-order grammars [27, 35] formally, since the technicalities needed for this small example are quite intuitive: generates a block of code at indentation level . The block can either be empty, contain one line followed by the rest of this block at the same indentation level, or start a new block at a greater indentation level. The symbol is used to generate a sequence of space characters ‘  ’. Finally, generates a program as a block at some initial indentation level. An example word, generated from this grammar and formatted in order to visualise indentation best, is shown in the middle of Fig. 4.

Suppose a word is given. This gives rise to an interpretation of the symbols in the grammar above as follows. Let be the lattice shown in Fig. 4 on the right. We use the following base functions, derived from the terminal symbols and the constructors in the higher-order grammar.

Intuitively, b, c,    and are used to mark sections of the word in which the corresponding symbols (resp. the empty word) occur. The top and bottom element of the lattice are used to signal true/false. The interpretation of these functions is therefore simply

for . The two constructors and denoting disjunctive choice and concatenation in the higher-order grammar are interpreted as follows.


Finally, we need two constants and which are interpreted as and , respectively.

The nonterminals in the higher-order grammar can be seen as (fixpoint) variables, hence we have . Then immediately becomes a second-order term of over