Programs may exhibit different behaviour depending on various circumstances. The environment can induce an effect upon program evaluation in many ways, e.g. nondeterministic decision making, access to some global store, or evaluation timeouts. Such effects can be represented as algebraic effect operations , which trigger an effect and capture all possible continuations.
General recursive computations with effect triggering operations can be seen as generating evaluation traces in the form of coinductive trees. Such trees have as leaves the potential results a computation can return, and as nodes effect operations which branch into each of the possible continuations. Due to the coinductive nature of these trees, they can be of infinite height, denoting potentially diverging computations.
In practice, when a program is evaluated, each of the effect operations is handled by choices made by the environment. An external observer, e.g. a user of a program, cannot directly inspect the generated tree. However, this spectator may make certain observations capturing effectful behaviour . Such observations include: possible termination, evaluation within a time limit, and how a program alters a global state.
We capture such observations with a collection of predicate liftings [28, 19]. These consist of a set of tokens denoting each possible observation, and for each token a device that lifts a predicate on return values to a predicate on coinductive trees over these values. Using a simple test logic, these predicate liftings are described “locally” in terms of effect operations. The local definitions generate an inductive predicate lifting, which captures a notion of effectful termination, and is used to specify total correctness with respect to the appropriate observation. The predicate liftings are similar to the ones derived from ordered monads by Hasuo .
Dually, we also generate coinductive predicate liftings. These allow computations to satisfy observations either immediately, or by postponing the burden of proof, potentially indefinitely. As such, they capture a notion of effectful divergence, and can be used to specify partial correctness with respect to effect observations.
A main contribution of this paper is formulating the coinductive predicate liftings in such a way that they can be used as a constructive notion of negation for the inductive predicates. As a result, these coinductive predicate liftings can be used to disprove inductive properties, and vice versa. This complementing pair hence helps us recover in our constructive setting some of the expressive power available when reasoning classically as in .
From the inductive and coinductive predicate liftings, we generate a logic for higher-order programs, extending the logic from  with coinductive predicate liftings (modalities). This logic captures behavioural properties (observations) for program denotations, and is endogenous in the sense of Pnueli . It is possible to build nested statements, mixing inductive and coinductive properties. The logic gives rise to a notion of program equivalence on program denotations, which subsumes a notion of applicative bisimilarity in the sense of [4, 13].
We use predicate liftings and the resulting logic to study programs exhibiting various different effects, including: evaluation cost, nondeterminism, global store, and user input. The adaptive nature of the generic formulations allows for many more examples to be implemented. As such, we believe it gives a foundation for the verification of effectful programs in many situations.
In particular, we study the preservation of the logical program equivalence over certain program transformations. One such transformation prevalent for higher-order programs is that ofsequencing, as used e.g. by let-binding, program composition and the uncurrying operation. We show that, for our effect examples, a property for a sequenced program can be decomposed into (or pulled back to) a property for unsequenced programs. This adapts the notion of decomposability featured in , and gives us a variety of proof techniques for the verification of higher order programs.
The development in this paper has been fully formalized in the Agda proof assistant. The code is freely accessible at: https://github.com/niccoloveltri/ind-coind-pred-lifts. It uses Agda 2.6.1 with standard library 1.6-dev. One consequence of this formalization is that all the behavioural properties described by our predicate liftings and logic are fully constructive. Programming languages that can be deeply embedded in Agda can be studied using this logic too. For instance, we have an implementation of an effectful version of fine-grained call-by-value PCF [24, 17] with its own variation on the logic similar to the one used in .
Basic Type-Theoretic Definitions and Notation. Our work takes place in Martin-Löf type theory extended with inductive and coinductive types, and practically in the Agda proof assistant. We use Agda notation for dependent function types: . We write for the empty type and for the unit type with unique inhabitant . We use for the type of Booleans with two inhabitants . We write as a synonym for when we want to use a specific name instead of . Similarly, we write for the synonym of where and are replaced by and . We use for the type of finite lists of elements of type . Propositional equality is and judgemental equality is (as in Agda). Types are stratified in a cumulative hierarchy of universes . The first universe is simply denoted and when we write statements like “ is a type”, we mean for some universe level . For readability reasons, in the paper all mentions of universe levels have been removed. We often employ the proposition-as-types perspective, and write statements like “ implies ”, which formally expresses the existence of a function from type to type , or “ holds”, meaning that there exists an element of type .
2 Programs as Trees
The type of coinductive trees is generated by two constructors:
A coinductive tree can either be of the form , for a value of type , or , where represents the immediate subtrees of . The branching is specified by the type . Following Leroy and Grall’s notation , we use the double line in the inference rule of to indicate that this is a coinductive constructor, and as such it can be iterated an infinite amount of times. For example, assuming with , the corecursive definition of the infinite tree with exactly one branch at each level goes as follows: .
In Agda, coinductive types are encoded as coinductive records which can optionally be parametrized by a size to ease the productivity checking of corecursive definitions [3, 9]. For example, the type of trees given above is implemented in Agda as the following pair of mutually defined types:
The type is inductive, while is a coinductive record type. The inductive constructors and are similar to the inference rules in (1), but now the subtrees in have return type . Elements of the latter type are like thunked computations that can be resumed by feeding a token, in the form of a size smaller than , to the destructor . The resumed subtree inhabits the type . The decrease in size can be explained by viewing sizes as abstract ordinals representing the number of unfoldings that a coinductive definitions can undergo. Sizes come with a top element , and a fully formed coinductive tree has type . This means that a user has an infinite number of tokens that she can spend for accessing arbitrarily deep subtrees of . The presence of sizes in the types of coinductive records is crucial for ensuring productivity of corecursively defined functions, which would otherwise need to pass Agda’s strict syntactic productivity check.
In this paper, we opt to work with an informal treatment of coinductive types, specified by constructors as in (1). Accordingly, corecursive functions are given as usual in terms of recursively defined functions. In particular, all mentions to sizes in types have been dropped, but the interested reader can recover them in our Agda formalization.
The datatype is a functor, and we call its action on functions. It is also a monad, with unit and multiplication corecursively defined as:
In our programs-as-trees perspective, elements in a leaf position represent return values of computations, while each node in a tree denotes the presence of an effect operation. is the type of admissible operations and, for each , is a type corresponding to the arity of operation . The existence of a skip operation with enables the encoding of possibly non-terminating computations, as done in , like the diverging program introduced previously in this section. If is equivalent to the unit type and is its unique inhabitant, then corresponds to the type of deterministic possibly non-terminating programs returning values of type after some number of steps.
As discussed in the last paragraph of the previous section, a user of a program represented by a tree in , with only one admissible skip operation , is able to observe the terminating and non-terminating behaviour of the program and its computation time. A user of a program represented by a nondeterministic binary tree may observe that some of the possible return values in the tree satisfy a certain property, or that none of them do. In the case of computer programs exhibiting a variety of different computational effects, the user can observe other relevant behaviour and therefore make appropriate queries to the system.
The collection of admissible queries on effectful programs in , capturing observations of effectful behaviour, is given by a particular class of tree predicates, generated by a predicate tree lifting. We introduce an inductive grammar of logical statements, whose elements we call tests, to define these predicate liftings.
Elements of are interpreted as types via the lifting . Atoms, i.e. tests of the form , are modelled as where is the input predicate. The other constructors of are modelled via Curry-Howard correspondence. E.g. conjunction of tests and is interpreted as the Cartesian product of the interpretations of and , and similarly for all the other logical operations. The type former is a functor, we call its action on functions.
Observations on coinductive trees are formulated using a threefold specification:
A type of tokens, each denoting a particular observation or test on an effectful program.
A decidable subset specifying which observations are satisfied by the production of a successful result. This is called the leaf function for .
A function formulating when a tree with a root node satisfies an observation. This is called the node function for . Note that the constructors of the grammar allow for both parallel and sequenced testing.
To each observation and tree , which we think of as obtained from a tree in after an application of a predicate to its leaves, we wish to associate a type corresponding to the handling of . The result should be a particular logical combination of the truth values from dictated by the input observation . This is done in the following definition:
Given and , we define the -indexed algebra inductively as follows:
The satisfiability of the predicate by a tree depends on the shape of . If , for some , then it satisfies in case holds and the token is correct according to the leaf function . If , for some and , then the test specifies a combination of continuations and new observations . If subtrees satisfy , according to the logical formula corresponding to , then also satisfies . The use of the inductively-defined grammar of logical statements and its interpretation in types in the premise of guarantees that is a well-defined inductive type family and passes Agda strict positivity check.
The inductive -indexed predicate lifting of a tree given by is defined as
Here we see the predicate as some observable property on values of type . For each observation , lifts this property to an extensionally observable property on computations of type .
For each effect, an -indexed predicate lifting is specified according to two design principles:
All predicate liftings in the family capture a notion of observation which is extensionally observable by a user of the program.
Together, the predicate liftings are powerful enough to express differences between programs that might be detectable after certain canonical program transformations.
Later in this paper, we will formulate a notion of behavioural equivalence specified by this family of observations. The second design principle expresses the desire that this behavioural equivalence is preserved by certain program transformations. One such transformation of particular interest to us is that of program sequencing.
We start with purely deterministic programs, represented by trees in where and ; that is, trees have only one node label which has only one continuation. The type is therefore equivalent to Capretta’s delay monad applied to . The label corresponds to a deterministic program step, which may or may not be observable by the user of the program. A family of observations is chosen depending on whether the skip is observable.
If skips are unobservable, we can only observe termination. We take and define , meaning that we observe when the computation terminates, and , meaning that we keep observing on the continuation of a not-yet-terminated computation. In this case, is provable when eventually produces a leaf such that holds.
Alternatively, we may consider to be observable, for instance as a measure of evaluation time. We use as observations expressing time limits on termination. We take , and , making observe termination within at most skips.
Consider an unpredictable scheduler making binary decisions for a program.
Such computations are denoted by binary decision trees
binary decision trees, i.e. coinductive trees in over signature , , whose nodes display choices given by the operation. Due to the unpredictability of the decisions made, we interpret these choices as being resolved nondeterministically. Nondeterminism in functional languages has been thoroughly studied by Ong  and Lassen  and many others.
Users of nondeterministic programs cannot observe decision paths. They may at most observe what may be possible, and what must happen. As such, we formulate two observations , with accompanying functions , and . In this case, holds if there is some sequence of resolutions of choices for which produces a result satisfying . On the other hand, holds if is guaranteed to produce a result satisfying , no matter the decisions made.
Suppose programs can consult some global store containing a natural number, using a and an update operation . The lookup operation is countably branching, with , and continues according to the current state. For any , stores in the global store, and continues in a unique way, defining . Computations in express communication patterns between a program and a global store, potentially yielding a result of type .
We suppose that both the state before the execution of a program and the state after the execution of a program are observable. As observations, we use pairs of initial and final states. With we aim to express that, when program is evaluated with starting state , it produces a result satisfying with the final state equal to . To this end, we define:
, but if , since termination does not change the state,
, and .
In this last example, we consider computations which repeatedly ask input bits from a user. We have one operation of arity . The user chooses inputs for the program, and we specify two sorts of observations. For any list of bits of length , the user can check:
whether the computation terminates after the sequence of bits is entered;
whether after entering the sequence of bits , the computation requests another input.
Note that the user can only input the sequence one by one, and only if the computation keeps requesting more inputs until the bitlist is exhausted. Observations consist of a bit denoting which of the two tests are performed and the list of bits used in the test. We implement the observations as follows:
For any observation in the examples above, the predicate expresses some effectful variation on termination checking, such as termination within some time limit, decision invariant termination, and termination with a certain final state. The particular relevant notion of termination is dependent on our interpretation of the effect. We say that captures a notion of effectful total correctness according to observation .
The inductive predicates generated by are however unable to ‘detect’ the dual to termination: divergence. From the user’s perspective, divergence is mainly the absence of termination, the indefinite continuation of a program. It is not possible to extensionally confirm whether a program diverges. For some programs however, like in the given example , we can prove intensionally that a program will go on forever. Following this example’s lead, we will express provable effectful divergence next, using coinductive predicate liftings.
To prove correctness of a program, we either show that it terminates and produces a correct result, or we show it requires the resolution of some (effect) operation, in which case we may postpone the burden of proof to after this operation has been resolved. In total correctness, we must verify that the program will eventually terminate. In partial correctness however, we can get away with postponing the burden of proof indefinitely. In that case, if the program diverges, it is still considered correct, since it will never return an incorrect result.
We implement this notion of partial correctness via coinductive predicate liftings
, a dual to the inductive predicate liftings from last section. This additionally provides a technique for disproving inductive properties. For example, to disprove that a program produces an even number, we can either show that the program produces an odd number, or that the program diverges. In other words, if it is partially incorrect that a program produces an even number, then we can conclude that it is not totally correct that it produces an even number. Using coinduction, we set up this notion of partial correctness in a constructive way, with the additional motivation to use it as a constructive notion of ‘negation’. However, due to decidability issues, this will never truly be a perfect negation, only a generically large constructive complement.
Given and , we define the -indexed algebra coinductively, using the following judgments:
When a program returning values of type terminates immediately, there are two possible ways of satisfying : either the program returns an inhabited type (as in the constructor ) or is incorrect according to (as in the constructor ). Conceptually, states that we do not need to verify correctness of the result if we are not currently verifying an observation which we consider ‘correct for termination’.
For instance, in Example 6 of global store, expresses the following partial correctness property: if with starting state the program terminates with final state , then it produces an inhabited type. This statement could be satisfied with an exception “”, which entails that the program terminates with a final state we are not currently checking for. In that case, the property is satisfied since the condition we are testing for at termination is not met. Hence is inhabited.
The double line in the constructor reflects the coinductive nature of the predicate lifting. In the Agda implementation, is formulated using mutual induction-coinduction, similarly to the encoding of coinductive trees in (2). In particular, sizes appear as extra arguments to ensure productivity of corecursive definitions. From we can extract a proof in terms of proofs on the continuation . Since is a coinductive constructor, it is possible for a proof of to contain an infinite amount of such extractions. Hence, proofs of can be self-referential, and can refer to infinitely many nodes in the tree.
The coinductive -indexed predicate lifting of a tree given by is defined as:
The coinductive -indexed predicate lifting specify notions of partial correctness. In an effect-free language, this amounts to saying that, if the program terminates, it produces a correct result. For effects, it captures more general notions of partial correctness, checking correctness of a result only at termination and only under certain circumstances. These circumstances are given by observations for which .
Partial correctness is useful both in cases in which termination can be checked independently, or when one is more interested in the safety of results at the expense of possible divergence. Considering non-terminating programs as at least not dangerous, gives more freedom when trying to design safe programs, and the coinductive predicate liftings give tools for the verification of such programs in many different effectful situations.
4.1 Coinduction as Negation
As mentioned before, another main use for coinduction is as a notion of negation for inductive properties. As such, it also has uses when studying and reasoning about inductive predicates. But coinduction cannot always directly be used this way. We need to modify its formulation slightly.
In this paper, we introduce effectful versions of termination and divergence, which result in effectful versions of total and partial correctness complementing each other. Considering that effect observations are formulated using tests, in order to properly function as a notion of constructive complement, we need to formulate the complement or dual of a test, called .
Following well-established results in logic, this function can be used to give a constructive complement. This is formulated by showing that it lifts disjoint predicates on to disjoint predicates on .
Two predicates on are disjoint if there is a proof of . A predicate lifting over is a function . Two predicate liftings are distinct if for any two disjoint predicates and on , the pair of lifted predicates and are disjoint.
The predicate liftings and on are distinct.
We use this dualization of tests to motivate a particular specification for our formulation of . If for we use and , then we will use and as specification for the formulation of . In this case, the premise of in Definition 8 unfolds to the type .
Note that there are multiple complementing pairs for each set , since the specifications in terms of and may vary. We do this in order to establish the following result.
Suppose is a complementing pair of -indexed predicate liftings, then for any , the predicate liftings and on are distinct.
Let and be two disjoint predicates on , and let be a coinductive tree. We need to show that it is absurd to assume both and
. The proof proceeds by pattern matching on.
If , then must be proved by for some and . Similarly, must be proved by either 1) for some , hence with and disjointness of and , we get a proof of absurdum; or 2) for some , hence by transitivity and symmetry applied to and .
If , proofs of and are required to be of the form and with and . By inductive hypothesis, the predicates and are disjoint. And this, together with the presence of both proof terms and , is absurd by Lemma 11. ∎
Notice that the above proof is finite, in the sense that the term of inductive type gets smaller in each recursive call.
Let us now look at our running examples. Remember that we dualize the tests of our observations.
Consider Example 4 concerning pure computations using the operation. In case is undetectable, and we have only one observation for termination, expresses partial correctness of : either terminates producing a result correct under , or diverges producing infinitely many skips. We get that implies . In the case that the s can be counted and , expresses partial correctness of under time limit : either terminates within skips producing a result correct under , or takes at least skips. We get that implies .
Consider Example 5 concerning nondeterministic computations using the binary choice operation . Then states that it is not possible to get a result which does not satisfy , so any decision process for must either lead to divergence or the production of a result correct under . On the other hand, says that we cannot guarantee that produces a result which does not satisfy , so either may diverge or it may produce a result correct under .
Note that swaps the and definitions in . As such, acts like a partial correctness version of must, and acts like a partial correctness version of may. As a result, implies , and implies .
We already briefly discussed the coinductive predicate lifting generated by global store observations from Example 6. Then states that: if with starting state the program terminates with final state , then it produces a result correct under . We have that implies . Moreover, due to the exception base case , we have that for , implies . These predicates vary from more traditional partial correctness properties for global store from the literature. However, with the logic in Section 5, these alternative formulations can be reconstructed.
Lastly, we consider the input requesting computations from Example 7. These use as observations , a pair consisting of a bit and a list of bit inputs. Then expresses the partial property telling us that: if can be given the input sequence and if terminates after it is given, then it returns a value satisfying predicate . On the other hand, says: if the user is able to input the sequence , then the computation will not ask for another input afterwards. Note that neither nor concern themselves with the predicate .
A Note on Computability. The coinductive counterpart to the inductive property does not offer a complete notion of negation, such as is offered by the more usual functions into the empty datatype . It is however a large constructive distinct property, which allows for double negation elimination. We briefly explore the difference between the two notions of negations.
Consider a possible enumeration of pairs of all Turing machines and their input arguments, and a function returningon the ones that terminate on their input. In other words, we consider a function , where is the signature with skips of Example 4. Consider the always true and always false predicates . One can construct a function which for gives proofs that the -th Turing Machine terminates on its input, and also a function which for gives proofs that the -th Turing Machine diverges. The two predicates on are necessarily disjoint. Moreover, by the Halting problem, they cannot partition either. Hence, they are not complete negations of each other.
This program can also be adapted to show differences between distinctions for other examples. In nondeterminism, lies in the gap between and . The program , where is a provably always diverging computation, lies in the gap between and .
5 Behavioural Logic
In this section we introduce our generic programming language of denotations and formulate a logic for expressing behavioural properties of programs in this language. These are meta-theoretic programs as can be given in Agda terms, rather than one specific programming language. As such, it is similar to other generic languages based on coinductive trees, like those formulated around interaction trees . Their type is specified by the following small grammar, also appearing in Moggi’s monadic metalanguage :
The collection of these syntactic types is called , and it includes names for the type of natural numbers, function type, Cartesian product and a unary type former for turning computations into values, often present in call-by-value languages. Well-typed terms of syntactic type are elements of type , where is a Boolean used to distinguish value terms from computation terms. Notably, computation terms of syntactic type are coinductive trees returning -typed values in their leaves. Value terms of syntactic type are exactly computation terms of syntactic type .
Our behavioural logic is composed of value formulae and computation formulae. Formulae are elements of type , where is either (value) or (computation) and is a syntactic type, which are inductively generated by the following inference rules:
We assume is a complementing pair of -indexed predicate liftings. We define satisfiability as a relation between syntactic terms in and formulae in .
The formulation of the logic and the satisfiability relation is quite standard , with a few exceptions. Formula formers and play the role of modalities. E.g. in the case of Example 5, and correspond to modalities and , and and are coinductive variants taking into account possible non-termination. The formula former allows to construct formulae via the simple test logic . For function testing, we opted for a concrete argument passing test as used in . This follows traditional testing approaches as used in applicative bisimilarity  and corresponding testing logics. Alternatively, Hoare-style predicates based on preconditions could be utilized. However, it seems in practice these are both cumbersome and difficult to work with.
The logic can be used for testing a variety of useful properties. Particularly interesting is the nesting of inductive and coinductive predicates, obtained by putting the predicates in a sequence. Using combinations of predicate liftings, we can easily swap between inductive and coinductive at different type levels, as needed. As an example, using countable skips from Examples 4 and 14, we can formulate a formula capturing the following property of programs of type : if we evaluate both the program and the result it produces, it will not take longer to evaluate the result then it took to evaluate the initial program. This can for instance be constructed as follows: .
Satisfiability of formulas is employed in the specification of an extensional ordering on syntactic terms. Given , we define the logical approximation . Program is below program in this ordering if and only if every formula satisfied by is also satisfied by . An extensional logical equivalence of syntactic terms is given by .
The negation of a formula is not among the generating connectives of the behavioural logic in (3). Nevertheless, an emergent notion of negation is admissible.
We define negation as a function using the following rules:
The base cases and are each others complements, and a similar relationship exists between and . Negation is involutive in the following sense: for all formulae , we have the equivalence . Notice in particular that the double negation of a formula is syntactically equal to , not merely related by some notion of logical equivalence.
The use of the word “negation” for the formula operation is justified by the fact that no syntactic program satisfies simultaneously a formula and its complement .
For all , the predicates and are disjoint.
Given a term , we need to show that assuming both and is absurd. The interesting case is (and dually , which is proved in a similar way). In this case, the two assumptions rewrite to and respectively. By inductive hypothesis, and are disjoint, therefore invoking Proposition 13 generates a contradiction. ∎
The logical approximation is not symmetric, since does not generally entail . The validity of seems also not sufficient for deriving , i.e. a doubly-negated variant of . What can be proved from instead is the impossibility of and to satisfy dual formulae: for any formula , it is not the case that both and are derivable. This suggests the introduction of a weak notion of logical approximation , so that implies . Unlike the logical approximation , the relation is symmetric, which is easily provable invoking the involutive property of negation .
Alternatively, we could consider a logic with only inductive predicate liftings like in , and a logic with only coinductive predicate liftings. The negation operation is a function between the two logics, which sends a formula of one logic to its constructive complement. Even if one only considers behavioural equivalence under one of these logics, statements like may be verified or falsified using the constructive negation of from the other logic. As such, coinductive statements enrich our toolkit for reasoning about pre-existing notions of equivalence based on induction.
Higher-order computations can return values which are computations themselves. Using the logic of the previous section, we can formulate behavioural properties of such computations. Properties of higher-order programs may contain multiple inductive and/or coinductive predicate liftings in succession, allowing for nuanced statements.
One thing we can do with a higher-order program is sequence it. This evaluates the program, and then continues by evaluating its result, thereby putting the two evaluations in sequence. The question is, can we prove whether a sequenced program satisfies some behavioural property, using behavioural properties of the unsequenced higher-order program? Answering this question is fundamental for establishing a plethora of compositionality results.
To simplify the question, we consider double trees of type . These trees occur naturally as a result of, for instance, the study of computations of type using a value formula on . The sequencing of programs corresponds to an application of the monad multiplication map . Both single trees in and double trees have natural relations of extensional ordering:
For we say if for any , implies , and implies .
For we say if for any , implies , and implies .
Statements of the form and are called observational towers, hinting at the fact that these can be extended to include even more layers of predicate liftings. We express preservation of observations over program sequencing as follows.
We call a triple strongly decomposable if for any two double trees , implies .
One way of establishing strong decomposability is by showing that any observations and on sequenced programs can be decomposed into a test whose atoms are given by pairs representing observational tower statements.
We say that is an -decomposition if, for any , iff