Skip to main content
Erschienen in:

Open Access 2025 | OriginalPaper | Buchkapitel

On the Relationship between Dijkstra Monads and Higher-Order Fixpoint Logic

verfasst von : Risa Yamada, Naoki Kobayashi, Ken Sakayori, Ryosuke Sato

Erschienen in: Programming Languages and Systems

Verlag: Springer Nature Switzerland

Aktivieren Sie unsere intelligente Suche, um passende Fachinhalte oder Patente zu finden.

search-config
loading …

Abstract

Dieses Kapitel bietet eine eingehende Analyse der Beziehung zwischen Dijkstra-Monaden und Higher-Order Fixpoint Logic (HFL (Z)) bei der Verifikation von Funktionsprogrammen höherer Ordnung. Es beginnt mit der Einführung zweier unterschiedlicher Ansätze: des Dijkstra-Monadenansatzes, der halbautomatisch ist und auf Inferenz- und SMT-Lösungen beruht, und des HFL (Z) -Ansatzes, der vollautomatisiert ist und eine Fixpunktlogik höherer Ordnung verwendet. Das Kapitel untersucht die Ähnlichkeiten zwischen diesen Methoden, insbesondere im Umgang mit Prädikatstransformatoren und der Programmverifizierung. Sie formalisiert eine präzise Übereinstimmung zwischen den beiden Ansätzen für ein eingeschränktes Fragment einer Funktionssprache höherer Ordnung und zeigt, dass jede Methode äquivalente Prädikatstransformatoren ableiten kann. Das Kapitel behandelt auch die Hauptunterschiede zwischen den Ansätzen, insbesondere im Umgang mit verschachtelten Monaden und rekursiven Programmen. Er hebt die Stärken und Grenzen der einzelnen Methoden hervor und stellt fest, dass der Dijkstra-Monadenansatz Typbeschreibungen für rekursive Funktionen erfordert, während der HFL (Z) -Ansatz mit zeitlichen Eigenschaften umgehen kann, aber unter Ineffizienz und Unvorhersehbarkeit leiden kann. Das Kapitel schlägt eine halbautomatische Verifikationsmethode vor, die die besten Aspekte beider Ansätze integriert und partielle Anmerkungen und modulare Verifikationen ermöglicht. Diese Methode erweist sich als skalierbarer und berechenbarer als der vollautomatische HFL (Z) -Ansatz und erfordert weniger Anmerkungen als der Dijkstra-Monadenansatz. Das Kapitel schließt mit einer Diskussion der damit verbundenen Arbeit und des Potenzials für eine zukünftige Integration zwischen den beiden Verifikationstechniken.

On the Relationship between Dijkstra Monads and Higher-Order Fixpoint Logic

Risa Yamada1  , Naoki Kobayashi1  Orchid, Ken Sakayori1  Orchid and Ryosuke Sato2  Orchid
© The Author(s) 2025
Viktor VafeiadisProgramming Languages and SystemsLecture Notes in Computer Science1569510.1007/978-3-031-91121-7_16

We study the relationship between two approaches to higher-order program verification: a semi-automated method using Dijkstra monads and a fully automated method using a higher-order fixpoint logic called HFL(Z). Although the origins of both approaches are quite different, there are some striking similarities: both convert programs to corresponding predicate transformers, and the conversion is essentially obtained by a CPS transformation. After reviewing the two approaches, we formalize an exact correspondence between the two for a restricted fragment of a functional language. We also point out that, outside the restricted fragment, there are some important differences between the two approaches, suggesting the need for cross-fertilization to obtain the best of the two approaches. As an example of the cross-fertilization, we also propose a semi-automated verification method, which requires less annotations than the Dijkstra monad approach and can scale to larger programs than the HFL(Z) approach.

Keywords

Program Verification Dijkstra Monad Higher-Order Fixpoint Logic Predicate Transformer
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figa_HTML.png
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figb_HTML.png

1 Introduction

This paper studies the relationship between two approaches to the verification of higher-order functional programs: one based on Dijkstra monads [1, 12, 15] and the other based on HFL(Z) [9, 17].

The Dijkstra monad approach, introduced by Swamy et al. [15], is a semi-automated verification method for programs with higher-order functions and monadic effects. It associates a program with a corresponding predicate transformer in the form of a monad. Using the monad, the verification condition for a program can be computed through type inference, and discharged by SMT solvers. The method is only semi-automated; the type inference procedure requires type annotations for recursive functions. Kobayashi et al.’s approach based on HFL(Z) [9, 17] is a fully-automated approach to the verification of temporal properties of higher-order programs. In this approach, a functional program is automatically transformed to a formula of HFL(Z), a higher-order logic with fixpoint operators and integer arithmetic, so that the program satisfies a given temporal property just if the formula is valid. The validity of the formula is then checked by an automated solver for HFL(Z) validity checking [5, 6, 8].

The two approaches above are seemingly different, but there are some strong similarities. Consider the successor function defined by \(\texttt {let~succ~x = x+1}\).1 The Dijkstra monad approach (DM approach, for short) assigns the type \(x\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;\lambda p.p(x+1)\) to the function, where \(\texttt{D}\;\texttt {int}\;\lambda p.p(x+1)\) is a monadic type indexed by the predicate transformer \(\lambda p.p(x+1): (\texttt {int}\rightarrow \texttt {prop}) \rightarrow \texttt {prop}\). The type means that the function takes an integer \(x\), and for the return value \(r\) to satisfy the postcondition \(p(r)\), it suffices that the precondition \(p(x+1)\) holds. Thus, if we wish to prove \(\forall x.x>0\Rightarrow \texttt {succ}\ x>0\), it suffices to prove \(\forall x.x>0 \Rightarrow (\lambda p.p(x+1))(\lambda r.r>0)\), which is equivalent to \(\forall x.x>0 \Rightarrow x+1>0\).2

In the HFL(Z) approach, for the same program, the call-by-value CPS transformation is first applied to obtain a corresponding call-by-name program \(\texttt {let~succ\_cps~x~k = k(x+1)}\). Then the function succ_cps is converted to the corresponding predicate \(\textsf{Succ} := \lambda x.\lambda k.k(x+1)\), and the specification \(\forall x.x>0\Rightarrow \texttt {succ}\; x>0\) is mapped to the HFL(Z) formula \(\forall x.x>0\Rightarrow \textsf{Succ}\;x\;(\lambda r.r>0)\). In the example above, one can observe that the predicate \(\textsf{Succ}\;x\) is essentially a predicate transformer of the original function \(\texttt {succ}\); indeed, \(\textsf{Succ}\;x = (\lambda x.\lambda k.k(x+1))x\) is \(\alpha \beta \)-equal to the predicate transformer \(\lambda p.p(x+1)\) obtained in the DM approach. The key translation used to obtain \(\textsf{Succ}\) was a CPS transformation.3 Incidentally, Ahman et al. [1] have shown that various Dijkstra monads can automatically be obtained from effect monads by using the CPS transformation. In spite of these similarities, the two approaches have been studied independently.

In this paper, we first formalize a precise correspondence between the DM and HFL(Z) approaches, for a restricted fragment of a higher-order functional language parameterized by effect monads. We show that, for every predicate transformer derived in the HFL(Z) approach, an equivalent predicate transformer can be obtained also in the DM approach, and that, conversely for every predicate transformer derivable in the DM approach, an at least equally good predicate transformer can be obtained in the HFL(Z) approach.4 We also show that, outside the restricted fragment, there are at least two important differences between the two approaches. One is for programs containing “nested monads”, for which we point out some awkwardness in the DM approach: information about the program behavior is scattered and replicated in types. The other difference is for recursive programs: in the DM approach, a user has to specify the type of every recursive function, while in the HFL(Z) approach, there is no such need and predicate transformers are computed fully automatically, thanks to the use of a higher-order fixpoint logic. The latter difference yields some trade-offs: whilst the fully automatic approach of HFL(Z) frees users from burdens of annotating programs, it imposes a heavy burden on the backend HFL(Z) solver and thus makes the verification time highly unpredictable. Based on the latter difference, we also propose an integration of the two approaches, to take the best of two worlds. In the integrated approach, a user may provide type annotations for recursive functions selectively; thanks to the use of the fixpoint logic, not all recursive functions need to be annotated. Based on a refinement type system for HFL(Z) [6], we can automatically extract verification conditions from partially annotated programs. We have implemented a prototype tool, and confirmed its effectiveness: there are programs for which the verification tool based on the original HFL(Z) approach times out, but our tool succeeds without (unlike the DM approach) requiring full annotations for recursive functions.

Our main contributions are summarized as follows.

  • A formalization of the correspondence between the DM approach and the HFL(Z) approach, which have been studied independently.

  • Analysis of the differences between the two approaches, which suggests cross-fertilization of the two approaches.

  • A proposal of a semi-automated verification method to get the best of the two approaches. Our method is more scalable and predictable than the HFL(Z) approach, and works with fewer annotations than the DM approach.

In addition to these main contributions, our formalization includes generalized translation rules for the HFL(Z) approach to handle monadic effects, extending the example-based sketch of Kobayashi et al. [9, 17].

The rest of this paper is structured as follows. Section 2 reviews the two approaches based on Dijkstra monads and HFL(Z) and informally observes their similarities. Section 3 considers a restricted fragment of a higher-order functional language, and proves an exact correspondence between the two approaches. Section 4 discusses the differences between the two approaches. Section 5 presents our semi-automated verification method and reports preliminary experimental results. Section 6 discusses related work and Section 7 concludes the paper.

2 Overview of Dijkstra Monads, HFL(Z), and Their Relationship

This section reviews the Dijkstra monad approach and the HFL(Z) approach for higher-order program verification, and observes their similarities.

2.1 The Dijkstra Monad Approach

As mentioned already, Swamy et al. [15] introduced a semi-automated verification method targeting higher-order programs with monadic effects, based on a monad of predicate transformers called the Dijkstra monad. The types of effectful computations has the form \(\texttt{D}\; t\; w\), where \(\texttt{D}\) is the Dijkstra monad defined for each kind of effect, \(t\) is the pure type of the result, and \(w\) is a predicate transformer that describes the behavior of the computation by mapping postconditions to preconditions.

For example, consider the following program, which manipulates a state of type \(\texttt {int}\).
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figc_HTML.png

Dummy

The function \(\texttt{increment}\) gets the current state \(x\) and writes back \(x+1\), thus incrementing the state. The function is given the type:
$$\begin{aligned} \texttt {unit}\rightarrow \mathtt {D^{STATE}}\;{\texttt {unit}}\;{(\lambda s_\text {init}, post.\ post\, \texttt {()}\, (s_\text {init} + 1))}. \end{aligned}$$
The predicate transformer \(\lambda s_\text {init}, post.\ post\, \texttt {()}\, (s_\text {init} + 1)\) has the type \(\texttt {int}\rightarrow (\texttt {unit}\rightarrow \texttt {int}\rightarrow \texttt {prop}) \rightarrow \texttt {prop}\). The first parameter \(s_\text {init}\) stands for the initial state, and the second parameter \(post\) is the postcondition, which is a binary predicate on the result value and the final state. Given the parameters, the predicate transformer returns a precondition that guarantees the postcondition to hold after the computation. For example, to prove that the final state is positive after calling \(\texttt{increment}\), it suffices to apply the predicate transformer to the postcondition \(\lambda \texttt {()}. \lambda s_\text {final}.\ s_\text {final}>0\), and prove that the computed precondition \( s_\text {init}+1>0 \) holds before the call.

To derive a predicate transformer from a program, Swamy et al. [15] proposed a kind of a dependent type system and an inference algorithm. For programs without recursion, predicate transformers and the associated verification conditions can automatically be inferred, although users may optionally provide type annotations to specify the behavior of sub-expressions. For recursive functions, however, those annotations must be provided, as in other deductive (verification-condition-generation) approaches to program verification.

The type system is parametric in the choice of effects, so that the method can be applied to various effects as long as the definition of the Dijkstra monad is given, which consists of the predicate transformers of the monadic return and bind operators and additional effect actions. For example, to reason about effectful computations with an integer state, users just need to provide the following definitions; the system can then automatically (except for recursive functions) infer predicate transformers.
$$\begin{aligned} &\mathtt {D^{STATE}}: t:\texttt {type}\rightarrow w:\mathtt {W^{STATE}}\;t \rightarrow \texttt {type}\\ &\mathtt {W^{STATE}}\; t = \texttt {int}\rightarrow (t\rightarrow \texttt {int}\rightarrow \texttt {prop}) \rightarrow \texttt {prop}\\ &\mathtt {return^{STATE}} : x:t \rightarrow \mathtt {D^{STATE}}\; t\; (\lambda s.\lambda p.\ p\,x\,s)\\ &\mathtt {bind^{STATE}} : m:(\mathtt {D^{STATE}}\; t_1\; w_1) \rightarrow f:(x:t_1 \rightarrow \mathtt {D^{STATE}}\; t_2\; (w_2\; x)) \rightarrow \\ &\qquad \qquad \qquad \qquad \qquad \qquad \mathtt {D^{STATE}}\; t_1\; (\lambda s_0.\lambda p.\ w_1 s_0 (\lambda x.\lambda s_1.\ w_2\; x\; s_1\; p)) \\ &\texttt{get}: \texttt {unit}\rightarrow \mathtt {D^{STATE}}\; \texttt {int}\; (\lambda s.\lambda p.\ p\,s\,s) \\ &\texttt{put}: x:\texttt {int}\rightarrow \mathtt {D^{STATE}}\; \texttt {unit}\; (\lambda s.\lambda p.\ p\,\texttt {()}\,x). \end{aligned}$$
Here, the first line is the kind of the Dijkstra monad and the second line defines \(\mathtt {W^{STATE}}\;t\), the type of predicate transformers. The third and fourth lines define the types of monad operators, which specify how predicate transformers can be composed from those of sub-computations. The last two lines define predicate transformers for primitive operations on states. Actually, these definitions can be obtained partially automatically through the CPS transformation [1, 12]. The Dijkstra monad approach has been implemented in F\(^\star \) , and applied to the verification of real-world programs [2].

2.2 The HFL(Z) Approach

Kobayashi et al. [9, 17] proposed an approach to higher-order program verification based on HFL(Z), an extension of Viswanathan and Viswanathan’s higher-order fixpoint logic [16] with integers. In that approach, a higher-order functional program with a regular-temporal-property specification is automatically translated into a formula of HFL(Z), so that the program satisfies the property just if the formula is valid. The validity of the formula is undecidable in general, but automated (but inevitably incomplete due to the undecidability) validity checkers for HFL(Z) have been implemented since then [5, 6, 8]; the approach thus realizes fully automated verification of higher-order programs.

By using fixpoint operators, the properties of recursive functions can be precisely expressed in the HFL(Z) approach, without any need for manual annotations. For example, consider the following function.
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figd_HTML.png

Dummy

The function \(\texttt{sum}\) computes the sum of the first \(n\) natural numbers if \(n\) is a non-negative integer, and diverges otherwise. If we are interested in the total correctness, it suffices to translate the function to the predicate \(\textsf{Sum}\) defined by the following fixpoint equation:
$$ \textsf{Sum} =_\mu \lambda n.\ \lambda k.\ (n=0 \Rightarrow k\; 0) \wedge (n\ne 0 \Rightarrow \textsf{Sum}\; (n-1)\; (\lambda s.\ k\; (n+s))). $$
Here, the equation defines \(\textsf{Sum}\) as the least predicate that satisfies the equality. The above equation is automatically obtained from the function definition by applying the CPS transformation (note that \(k\) above represents a continuation), and by encoding the conditional expression into the corresponding logical formula. \(\textsf{Sum}\;n\) can be viewed as a predicate transformer; it takes a postcondition \(k\), and returns the corresponding precondition on \(n\) for \(\texttt{sum}\) to terminate and for the return value \(r\) to satisfy \(k\;r\). The total correctness is thus expressed as \(\forall n\ge 0.\textsf{Sum}\;n\;(\lambda r.r=n(n+1)/2)\). If we are interested in partial correctness instead, then we just need to replace \(=_\mu \) with \(=_\nu \) in the above fixpoint equation, so that \(\textsf{Sum}\) is defined as the greatest predicate that satisfies the equation.
The HFL(Z) approach targets a higher-order functional language with general recursion, nondeterminism, and an abstract notion of events, but various other effects (such as states and exceptions) can be encoded by a combination with standard transformations for functional programs such as store passing translation and variants of CPS transformations. For example, to deal with the increment function in Section 2.1, we first apply the store-passing transformation (and inline get and put) to obtain:
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Fige_HTML.png

Dummy

We can then obtain the following HFL(Z) predicate \(\textsf{Inc}\) by using the CPS (and currying) transformation:
$$ \textsf{Inc} := \lambda \texttt {()}.\lambda s.\lambda k.k\,\texttt {()}\, (s+1). $$
Suppose we wish to prove that, given a non-negative initial state, the state after calling increment is positive. That is expressed by the formula \(\forall s\ge 0.\textsf{Inc}\,\texttt {()}\,s\,(\lambda \texttt {()}.\lambda s'.s'>0)\). Here, notice that \(\textsf{Inc}\,\texttt {()}=_\beta \lambda s.\lambda k.k\,\texttt {()}\, (s+1)\) matches the predicate transformer obtained in the DM approach in Section 2.1.

2.3 The Similarity between Dijkstra Monads and HFL(Z)

We have already seen similarities between the two approaches for first-order functions (succ in Section 1 and increment in this section). We discuss a higher-order case in this subsection. Let us consider the following twice function.
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figf_HTML.png

Dummy

In the DM approach, the function twice can be given the following type.
$$ \forall w_f. f\mathbin {:}(x:\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(w_f\;x))\rightarrow x\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(\lambda p.w_f\;x\;(\lambda y.w_f\;y\;p)). $$
Here, the type is polymorphic on the predicate transformer \(w_f\) for \(f\). Using \(w_f\), the precondition for the part \(f\,y\) (with respect to the postcondition \(p\)) is expressed as \(w_f\;y\;p\), and thus the predicate transformer for twice can be expressed as \(\lambda p.w_f\;x\;(\lambda y.w_f\;y\;p)\). Since succ has type \(x\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(\lambda p.p(x+1))\), we obtain the type \( x\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(\lambda p.w_\texttt{succ}\;x\;(\lambda y.w_\texttt{succ}\;y\;p)) \) for plustwo, where \(w_\texttt{succ}:=\lambda x.\lambda p.p(x+1)\). By reducing it, we obtain \( x\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(\lambda p.p(x+2))\). In the HFL(Z) approach, the function twice is converted to the following predicate \(\textsf{Twice}\):
$$ \textsf{Twice} := \lambda f.\lambda x.\lambda k.f\;x\;(\lambda y.f\;y\;k). $$
The part \(\lambda k.f\;x\;(\lambda y.f\;y\;k)\) corresponds to \(\lambda p.w_f\;x\;(\lambda y.w_f\;y\;p)\) from the DM approach. The function plustwo is converted to \(\textsf{Twice}\;\textsf{Succ}\), which can be simplified as follows.
$$\begin{aligned} \textsf{Twice}\;\textsf{Succ} & = (\lambda f.\lambda x.\lambda k.f\;x\;\lambda y.f\;y\;k)(\lambda x.\lambda k.k(x+1)) =_\beta \lambda x.\lambda k.k(x+2). \end{aligned}$$
The part \(\lambda k.k(x+2)\) corresponds to \(\lambda p.p(x+2)\). Thus, there are some correspondences between the DM and HFL(Z) approaches even at higher-order.
Table 1 summarizes the correspondence between the two approaches we have seen so far (with some \(\alpha \)-renaming). As clear from the table, HFL(Z) formulas can actually be extracted from types of Dijkstra monads. In a higher-order case, HFL(Z) formulas correspond to polymorphic types on predicate transformers. A formal connection is established in the next section.
Table 1.

Correspondence between DM types and HFL(Z) formulas

https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Tab1_HTML.png

Dummy

3 Correspondence between the Two Approaches

In the previous section, we reviewed the two approaches to higher-order program verification and observed their similarities informally. In this section, we formalize a concrete correspondence between the two approaches for a restricted fragment of a higher-order functional language.

We first set up a common language for comparing the two approaches in Section 3.1. We then formalize the derivation of predicate transformers in the DM approach in Section 3.2, and the derivation of logical formulas in the HFL(Z) approach in Section 3.3. We then state and prove a correspondence in Section 3.4.

3.1 Language

This subsection introduces a simple call-by-value, effectful higher-order functional language, which shall be used as the common language for formally comparing the two approaches.

We exclude recursion and restrict the shape of types; as discussed in Section 4, some important differences show up for recursion and certain types.

The syntax of (monadic) types is given as follows:
$$\begin{aligned} a&:\,\!:= \texttt {unit}\mid \texttt {int}& t&:\,\!:= s\rightarrow r& r&:\,\!:= \texttt{M}\; a \mid t& s&:\,\!:= a\mid t\end{aligned}$$
Here, \(a\) is a base type for pure expressions, \(\texttt{M}\; a\) represents the type of effectful computations that return values of type \(a\). Inheriting the restrictions from the two approaches, the syntax of types above is restrictive compared to ordinary monadic type systems. As in the DM approach, a monadic type \(\texttt{M}\; a\) can only appear as the co-domain of a function type. The restriction of the parameter of \(\texttt{M}\) to a base type \(a\) comes from the restriction on the side of HFL(Z); this point will be discussed in Section 4. In addition to the restrictions originating from the two approaches, our formalization of the correspondence requires that monads must not be nested: for instance, \(\texttt {int}\rightarrow \texttt{M}\; (\texttt {int}\rightarrow \texttt{M}\; \texttt {int})\) is rejected. These restrictions still allow some higher-order function types: for example, \((\texttt {int}\rightarrow \texttt{M}\; \texttt {int})\rightarrow \texttt {int}\rightarrow \texttt{M}\; \texttt {int}\) is allowed. Further discussion on the restrictions can be found in Section 4.
The syntax of expressions is given by:
$$\begin{aligned} e &:\,\!:= \texttt {()}\mid n \mid x \mid \texttt{op}(\widetilde{e}) \mid \lambda x\mathbin {:} {s}.\ e \mid e_1 e_2 \mid \texttt {if}\ \texttt{pred}(\widetilde{e})\ \texttt {then}\ e_1\ \texttt {else}\ {e_2} \\ &\mid \texttt {return}\ e \mid \texttt {let~}x=e_1\texttt {~in~}e_2 \mid {\texttt{call}^{ act }} \end{aligned}$$
Here, \(n\) ranges over the set of integers, \(\texttt{op}\) ranges over a set of operations on integers (such as addition), \(\widetilde{e}\) denotes a sequence \(e_1,\ldots ,e_k\) of expressions, and \(\texttt{pred}\) ranges over a set of predicates on integers (such as equality, and nullary predicates \(\textsf{true}\) and \(\textsf{false}\)). We write \(|\widetilde{e}|\) for the length of the sequence \(\widetilde{e}\), and write \(\operatorname {arity}(\texttt{op})\) and \(\operatorname {arity}(\texttt{pred})\) for the arity of \(\texttt{op}\) and \(\texttt{pred}\) respectively. We just write \(\texttt{pred}\) for \(\texttt{pred}()\) when \(\texttt{pred}\) is nullary. The set of expressions includes variables, \(\lambda \)-abstractions, applications, conditionals, a monadic return operator, and let-expressions. The expression \(\texttt{call}^{ act }\) invokes the effect action named \( act \), where \( act \) ranges over a set \(\texttt{A}\) of names of effect actions. We may omit the type annotations for \(\lambda \)-abstractions when they can be inferred from the context. Recursion is not included here, but will be discussed in Section 4.
We introduce a type system to reject inappropriate expressions such as \(1+\texttt {()}\). We call this type system the simple (monadic) type system, to distinguish it from the type system for Dijkstra monads introduced later. The typing rules are given in Figure 1, which define two kinds of judgments \(\varGamma \mathrel {\vdash }e:s\) and \(\varGamma \mathrel {\vdash }e:\texttt{M}\; a\), where the latter is for effectful computations. In the rules https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figh_HTML.gif and https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figi_HTML.gif , \(\varGamma \mathrel {\vdash }\widetilde{e}:\widetilde{\texttt {int}}\) means \(\varGamma \mathrel {\vdash }e_i:\texttt {int}\) for every \(i\in \{1,\ldots ,k\}\). In the rule https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figj_HTML.gif , \(t^{ act }\) denotes the type of the effect action \( act \) (which is introduced below).
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Fig1_HTML.png
Fig. 1.

Typing rules of the language

We have so far parameterized the language by effects. A concrete language and its semantics are determined by the parameters given below, which we call effect parameters. The semantics of effect operations are given in the form of predicate transformers; we assume the simply-typed lambda calculus with \(a\) and \(\texttt {prop}\) (which is the type of propositions) as base types and also with appropriate constants (such as logical connectives \(\wedge , \vee : \texttt {prop}\rightarrow \texttt {prop}\rightarrow \texttt {prop}\), the predicate \(\texttt{pred}: \texttt {int}^k\rightarrow \texttt {prop}\) for each \(k\)-ary predicate \(\texttt{pred}\) in the source language, and the operator \(\texttt{op}:\texttt {int}^k\rightarrow \texttt {int}\) for each \(k\)-ary operator \(\texttt{op}\) in the source language) for describing predicate transformers.

  • \(\texttt{W}\): The type operator that takes a base type \(a\) and returns the (simple) type of the predicate transformer for computations returning values of type \(a\).

  • \(\mathtt {return^{W}}\mathbin {:}\forall a.a\rightarrow \texttt{W}\;a\): The predicate transformer of a computation that takes a value of type \(a\) and just returns it without any effects.

  • \(\mathtt {bind^{W}}\mathbin {:}\forall a_1,a.(\texttt{W}\;a_1 \rightarrow (a_1\rightarrow \texttt{W}\;a) \rightarrow \texttt{W}\;a)\): The predicate transformer of a computation that takes \(w_1\) and \(w_2\) of types \(\texttt{W}\;a_1\) and \(a_1\rightarrow \texttt{W}\;a\) respectively, and pipes the result of the computation specified by \(w_1\) to the computation specified by \(w_2\).

  • \(\texttt{A}\): A set of (names of) primitive effect actions. We use the metavariable \( act \) to range over this set.

  • \(t^{ act }\): The (monadic) type of each action \( act \in \texttt{A}\). This describes the types of parameters and return values of each action.

  • \(w^{ act }\): The predicate transformer of each action \( act \in \texttt{A}\). It describes the behavior of each action. The simple type of \(w^{ act }\) must be consistent with \(t^{ act }\), in the sense that \(w^{ act }\) must have the type obtained from \(t^{ act }\) by replacing \(\texttt{M}\) with \(\texttt{W}\). For example, if \(t^{ act }= \texttt {unit}\rightarrow \texttt{M}\; \texttt {int}\), then \(w^{ act }\) must have type \(\texttt {unit}\rightarrow \texttt{W}\;\texttt {int}\).

In the field of the DM approach, the former three parameters are called a specification monad, and the latter three yield the DM type of effect actions. Methods to obtain these parameters in a correct-by-construction manner have been studied by Ahman et al. [1] and Maillard et al. [12]. According to Ahman et al.’s result, reinterpreted by Maillard et al., for effects that can be represented as monad transformers, these parameters can be obtained by applying the monad transformer to the continuation monad with \(\texttt {prop}\) as the answer type.

We give examples of effect parameters, some of which can be derived from monad transformers.

Example 1

The simplest case is the purely functional one. In this case, there are no effect actions: \(\texttt{A}=\emptyset \). The rest of the effect parameters can be defined as:
  • \(\texttt{W}\;a:=(a\rightarrow \texttt {prop})\rightarrow \texttt {prop}\)

  • \(\mathtt {return^{W}}\;(x:a):=\lambda p:(a\rightarrow \texttt {prop}).p\;x\)

  • \(\mathtt {bind^{W}}\;(w_1:\texttt{W}\;a_1)\;(w_2:a_1\rightarrow \texttt{W}\;a):=\lambda p:(a\rightarrow \texttt {prop}). w_1(\lambda x. w_2\; x\; p)\)

These coincide with the identity monad transformer applied to the continuation monad.

Example 2

For programs with a state of type \(\texttt {int}\), the effect parameters are:5
  • \(\texttt{W}\;a:={\texttt {int}} \rightarrow (a\rightarrow \texttt {int}\rightarrow \texttt {prop}) \rightarrow \texttt {prop}\)

  • \(\mathtt {return^{W}}\;(x:a):=\lambda h:\texttt {int}.\lambda p:(a\rightarrow \texttt {int}\rightarrow \texttt {prop}). p\;x\;h\)

  • \(\begin{aligned}& \mathtt {bind^{W}}\;(w_1:\texttt{W}\;a_1)\;(w_2:a_1\rightarrow \texttt{W}\;a) \\ &\quad :=\lambda h:\texttt {int}.\lambda p:(a_2\rightarrow \texttt {int}\rightarrow \texttt {prop}). w_1\;h\;(\lambda x:a_2.\lambda h':\texttt {int}. w_2\;x\;h'\;p) \end{aligned}\)

  • \(\texttt{A}=\{get,put\}\)
    • \(t^{get} :=\texttt {unit}\rightarrow \texttt{M}\; \texttt {int}\)

    • \(w^{get}:=\lambda ().\lambda h:\texttt {int}.\lambda p:(\texttt {int}\rightarrow \texttt {int}\rightarrow \texttt {prop}). p\;h\;h\)

    • \(t^{put} :=\texttt {int}\rightarrow \texttt{M}\; \texttt {unit}\)

    • \(w^{put}:=\lambda x:\texttt {int}.\lambda h:\texttt {int}.\lambda p:(\texttt {unit}\rightarrow \texttt {int}\rightarrow \texttt {prop}). p\;\texttt {()}\;x\)

These parameters can be obtained by applying the state monad transformer to the continuation monad. Note that \(w^{get}\) and \(w^{put}\) have types \(\texttt {unit}\rightarrow \texttt{W}\;\texttt {int}\) and \(\texttt {int}\rightarrow \texttt{W}\;\texttt {unit}\) respectively, obtained from \(t^{get}\) and \(t^{put}\) by replacing \(\texttt{M}\) with \(\texttt{W}\).

Given the effect parameters above, the semantics of the language can be given as a predicate transformer semantics. We omit the definition here, as the translation from programs to HFL(Z) formulas given in Section 3.3 can actually be viewed as the predicate transformer semantics (although the authors [9, 17] did not provide that observation).

3.2 The Dijkstra Monad Approach

The Dijkstra monad approach is formalized as another type system for the language, which we call the DM type system. We use \(\mathrel {\vdash ^\textrm{DM}}\) instead of \(\mathrel {\vdash }\) for a type judgment of the DM type system. The syntax of types is given below.
$$\begin{aligned} & a \text{(base } \text{ types) } :\,\!:= \texttt {unit}\mid \texttt {int}\\ & \pi \text{(predicate } \text{ transformer } \text{ types) } :\,\!:= a\mid \texttt {prop}\mid \pi \rightarrow \pi \qquad \\ & \tau \text{(function } \text{ types) } :\,\!:= x\mathbin {:} \varsigma \rightarrow \rho \mid \forall u\mathbin {:}\pi .\ \tau \\ & \varsigma \text{(argument } \text{ types) } :\,\!:= a\mid \tau \qquad \qquad \rho \text{(return } \text{ types) } :\,\!:= \texttt{D}\; a\; w\mid \tau \qquad \\ & w \text{(predicate } \text{ transformers) }:\,\!:= e \mid \texttt{pred}{(\widetilde{e})} \mid \lnot \texttt{pred}{(\widetilde{e})} \mid w_1 \wedge w_2 \mid w_1 \vee w_2 \qquad \\ &\qquad \qquad \qquad \qquad \qquad \mid \lambda u\mathbin {:}\pi .\ w\mid w_1 \;w_2 \mid {\texttt {if~}\texttt{pred}(\widetilde{e})\texttt {~then~}w_1\texttt {~else~}w_2} \end{aligned}$$
Compared with the simple monadic type system, we introduced the type \(\pi \) for predicate transformers, and changed the type of a monadic computation from \(\texttt{M}\; a\) to \(\texttt{D}\; a\; w\), where \(w\) is a predicate transformer of type \(\texttt{W}\;a\). The metavariable \(a\) is inherited from the simple type system, and the metavariables \(\tau , \varsigma \), and \(\rho \) correspond to \(t\), \(s\), and \(r\) respectively, in the simple monadic type system. Function types have been extended to dependent function types of the form \(x\mathbin {:} a_1 \rightarrow \texttt{D}\; a_2\; w\), etc., so that \(x\) may occur in the predicate transformer \(w\). We also allow polymorphic types \(\forall u\mathbin {:}\pi .\ \tau \) on predicate transformers \(u\). The language of predicate transformers, ranged over by \(w\), is defined by extending the expression syntax with constructs of predicate logic, \(\lambda \)-abstractions and applications of the type \(\pi \), and (non-monadic) conditional expressions; we assume that the language of predicate transformers subsumes the simply-typed \(\lambda \)-calculus assumed in Section 3.1 for describing predicate transformers. We may omit type annotations for predicate transformer variables and just write \(\lambda u.\ w\) and \(\forall u.\ \tau \) for \(\lambda u\mathbin {:}\pi .\ w\) and \(\forall u\mathbin {:}\pi .\ \tau \) when they are clear from the context. Note that this definition is a fragment of the original type system of Dijkstra monads [15]: for example, refinement types and polymorphism are excluded.
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Fig2_HTML.png
Fig. 2.

Typing rules of DM for expressions

The typing rules of the DM type system for expressions and predicate transformers are shown in Figures 2 and 3, respectively. In the rule https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figk_HTML.gif , \(\varsigma ^{\textrm{toM}}\) denotes the simple monadic type obtained by erasing predicate transformers, as defined by:
$$\begin{aligned} & a^{\textrm{toM}} :=a\quad (\texttt{D}\; a\; w)^{\textrm{toM}}=\texttt{M}\; a \quad \\ & (x\mathbin {:}\varsigma \rightarrow \rho )^{\textrm{toM}} :=\varsigma ^{\textrm{toM}}\rightarrow \rho ^{\textrm{toM}} \quad (\forall u\mathbin {:}\pi . \tau )^{\textrm{toM}} :=\tau ^{\textrm{toM}}. \end{aligned}$$
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Fig3_HTML.png
Fig. 3.

Typing rules of DM for predicate transformers

The rules DM-Ret and DM-LetIn reveal that the Dijkstra monad \(\texttt{D}\; a\; w\) is a monad indexed by the monad \(\texttt{W}\) of predicate transformers. The monadic operators \(\mathtt {return^{W}}\) and \(\mathtt {bind^{W}}\) of \(\texttt{W}\) are used in the typing rules of return expressions and let-in expressions, respectively. When a pure expression is lifted to an effectful computation using the return expression, it is injected into the predicate transformer using \(\mathtt {return^{W}}\). The predicate transformer of a let-expression is computed by composing the two predicate transformers of its two sub-expressions using \(\mathtt {bind^{W}}\).

In addition to typing rules guided by the expression syntax, we have rules https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figl_HTML.gif , https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figm_HTML.gif , and https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Fign_HTML.gif for conversion and introduction/elimination of universal quantifiers. The original type system of Dijkstra monads [15] has a subsumption rule, but we introduced the conversion rule instead for simplicity. The equivalence relation \(\varGamma \mathrel {\vdash ^\textrm{DM}}\varsigma =_{\beta \eta }\varsigma '\) is defined based on the \(\beta \eta \)-equality of predicate transformers inductively on the structure of types, where the base case \(\varGamma \mathrel {\vdash ^\textrm{DM}}\texttt{D}\; a\; w_1 =_{\beta \eta } \texttt{D}\; a\; w_2\) holds just if \(w_1 =_{\beta \eta } w_2\). The type of an action \(\texttt{call}^{ act }\) is constructed from the simple type \(t^{ act }\) and the predicate transformer \(w^{ act }\), using the transformation \(\operatorname {toDM}[r, w]\), proposed by Ahman et al. [1]. Intuitively, this transformation yields a DM type of an expression of type \(r\) whose behavior is specified by the predicate transformer \(w\). It is defined as follows.
$$\begin{aligned} &\operatorname {toDM}[\texttt{M}\; a, w] := \texttt{D}\; a\; w \\ &\operatorname {toDM}[a\rightarrow r, w] := x: a\rightarrow \operatorname {toDM}[r, w\ x] \\ &\operatorname {toDM}[t\rightarrow r, w] := \forall w_{x}: {t}^{\textrm{WP}}.\ x: \operatorname {toDM}[t, w_{x}] \rightarrow \operatorname {toDM}[r, w\ w_{x}] \quad (x, w_{x} \text {: fresh}). \end{aligned}$$
As we see in the third line, when \(r\) is an arrow type taking a function as the parameter \(x\), we universally quantify over the predicate transformer \(w_{x}\) of the function. The type of \(w_{x}\) is calculated from the simple type \(r\), defined below:
$$\begin{aligned} & {a}^{\textrm{WP}} := a\qquad {(\texttt{M}\; a)}^{\textrm{WP}} := {\texttt{W}\;a} \qquad {(s\rightarrow r)}^{\textrm{WP}} := {s}^{\textrm{WP}} \rightarrow {r}^{\textrm{WP}} \end{aligned}$$
Essentially, the translation replaces the monadic type \(\texttt{M}\; a\) with the type \(\texttt{W}\;a\). Note that the transformations \(\operatorname {toDM}[r, w]\) and \({r}^{\textrm{WP}}\) are called elaboration and the \(\star \)-transformation, respectively, by Ahman et al. [1]. Below we demonstrate how the DM type of \(\texttt{call}^{put}\) is calculated. Recall the definitions \(t^{put}=\texttt {int}\rightarrow \texttt{M}\; \texttt {unit}\) and \(w^{put}=\lambda x.\lambda h.\lambda p. p\,\texttt {()}\,x\) from Example 2.
$$\begin{aligned} \operatorname {toDM}[t^{put}, w^{put}] &=\operatorname {toDM}[\texttt {int}\rightarrow \texttt{M}\; \texttt {unit}, \lambda x.\lambda h.\lambda p. p\,\texttt {()}\,x] \\ &=x:\texttt {int}\rightarrow \texttt{D}\; \texttt {unit}\; ((\lambda x.\lambda h.\lambda p. p\,\texttt {()}\,x)\;x) \\ &=_\beta x:\texttt {int}\rightarrow \texttt{D}\; \texttt {unit}\; (\lambda h.\lambda p. p\,\texttt {()}\,x) \end{aligned}$$
A higher-order example of the calculation \(\operatorname {toDM}[t, w]\) is given later in Section 3.4. Note that the DM type \(\operatorname {toDM}[r, w]\) is consistent with the simple type \(r\), that is, \((\operatorname {toDM}[r, w])^{\textrm{toM}}=r\).

Example 3

Let us consider the following expression, which is equivalent to the function twice discussed in 2.3.
$$\begin{aligned} \lambda f: (\texttt {int}\rightarrow \texttt{M}\; \texttt {int}). \lambda x:\texttt {int}. \texttt {let~}y=f\;x\texttt {~in~}f\;y. \end{aligned}$$
This expression is typed as follows:
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figo_HTML.png

Dummy

Here, \(\varGamma := w_{f}:\pi _{w_{f}}, f:\tau _f,x:\texttt {int}\), where:
$$\begin{aligned} &\pi _{w_{f}}:= \texttt {int}\rightarrow \texttt{W}\;\texttt {int} & \tau _f:= z:\texttt {int}\rightarrow \texttt{D}\; \texttt {int}\; (w_{f}\;z)\\ &w_\texttt{twice}:= \mathtt {bind^{W}}\;(w_{f}\;x)\;(\lambda y. w_{f}\;y) & e_\texttt{twice}:= \texttt {let~}y=f\;x\texttt {~in~}f\;y\\ \end{aligned}$$

3.3 The HFL(Z) Approach

This subsection formalizes a translation from a program to a HFL(Z) formula, which represents the predicate transformer of the program. HFL(Z) is a higher-order logic with least/greatest fixpoints and integers, introduced by Kobayashi et al. [9] as an extension of higher-order modal fixpoint logic [16].

Syntax The syntax of HFL(Z) formulas is defined by:
$$\begin{aligned} \kappa \text{(types) } :\,\!:= & \texttt {prop}\mid \eta \rightarrow \kappa \qquad \eta :\,\!:= \texttt {unit}\mid \texttt {int}\mid \kappa \\ \varphi \text{(formulas) } :\,\!:= & \texttt {()}\mid n \mid x \mid \texttt{op}{(\widetilde{\varphi })} \mid \texttt{pred}{(\widetilde{\varphi })} \mid \lnot \texttt{pred}{(\widetilde{\varphi })}\\ \mid & \varphi _1 \wedge \varphi _2 \mid \varphi _1 \vee \varphi _2 \mid \lambda x\mathbin {:}\eta . \varphi \mid \varphi _1\; \varphi _2 \mid \mu x\mathbin {:}\kappa . \varphi \mid \nu x\mathbin {:}\kappa . \varphi . \end{aligned}$$
In addition to ordinary constructs of predicate logic, the syntax includes \(\lambda \)-abstractions and applications for manipulating higher-order predicates, and the least/greatest fixpoint operators \(\mu \) and \(\nu \). The formulas \(\mu x\mathbin {:}\kappa . \varphi \) and \(\nu x\mathbin {:}\kappa .\varphi \) respectively denote the least and greatest predicates of type \(\kappa \) such that \(x \equiv \varphi \). We sometimes use a fixpoint equation \(x\mathbin {:}\kappa =_\alpha \varphi \) (where \(\alpha \in \{\mu ,\nu \}\)) to define \(x\) as the predicate \(\alpha x\mathbin {:}\kappa .\varphi \). Actually, fixpoint formulas are required only for encoding recursive functions; thus, we do not use them in this section.

HFL(Z) [9] has an associated simple type system to exclude ill-formed formulas. The type system is essentially that of the simply-typed \(\lambda \)-calculus with operators, constant predicates, and logical connectives. A concrete definition of the type system is deferred to the longer version [19]. We assume that HFL(Z) contains, as a sublanguage, the simply-typed \(\lambda \)-calculus assumed in Section 3.1 for describing predicate transformers.

Reduction from Program Verification In this section, we introduce the translation from programs to HFL(Z) formulas.

We first define the translation \({(\cdot )}^{\textrm{HFL}}\) for types by:
$$\begin{aligned} & {a}^{\textrm{HFL}} := a\qquad {(\texttt{M}\; a)}^{\textrm{HFL}} := {\texttt{W}\;a} \qquad {(s\rightarrow r)}^{\textrm{HFL}} := {s}^{\textrm{HFL}} \rightarrow {r}^{\textrm{HFL}}. \end{aligned}$$
Actually, this transformation is the same as \({(\cdot )}^{\textrm{WP}}\), introduced in the DM type system for computing the type of predicate transformers from a simple monadic type. Thus, we use the notation \({r}^{\textrm{WP}}\) instead of \({r}^{\textrm{HFL}}\) hereafter.
The translation of expressions is defined by:6
$$\begin{aligned}& {\texttt {()}}^{\textrm{HFL}} := \texttt {()}\qquad {n}^{\textrm{HFL}} := n \qquad {x}^{\textrm{HFL}} := x \qquad {(\lambda x\mathbin {:}s.\ e)}^{\textrm{HFL}} := \lambda x\mathbin {:}{s}^{\textrm{WP}}.\ {e}^{\textrm{HFL}} \\ & {(\texttt{op}(\widetilde{e}))}^{\textrm{HFL}} := \texttt{op}(\widetilde{{e}^{\textrm{HFL}}}) \quad {(e_1 e_2)}^{\textrm{HFL}} := {e_1}^{\textrm{HFL}} {e_2}^{\textrm{HFL}} \\ & {(\texttt {if~}\texttt{pred}(\widetilde{e'})\texttt {~then~}e_1\texttt {~else~}e_2)}^{\textrm{HFL}} \\ &\qquad := {\textrm{IfThenElse}[\texttt{W}\;a,\texttt{pred}({\widetilde{e'}}^{\textrm{HFL}}),{e_1}^{\textrm{HFL}},{e_2}^{\textrm{HFL}}]} \\ &\qquad \qquad (\text {when } \varGamma \mathrel {\vdash }\texttt {if~}\texttt{pred}(\widetilde{e'})\texttt {~then~}e_1\texttt {~else~}e_2: \texttt{M}\; a) \\ & {(\texttt {return}\ e)}^{\textrm{HFL}} := {\mathtt {return^{W}}\;({e}^{\textrm{HFL}})} \qquad {(\texttt{call}^{ act })}^{\textrm{HFL}} := w^{ act }\\ & {(\texttt {let~}x=e_1\texttt {~in~}e_2)}^{\textrm{HFL}} := {\mathtt {bind^{W}}\;({e_1}^{\textrm{HFL}})\;(\lambda x.{e_2}^{\textrm{HFL}})}. \end{aligned}$$
Here, \(\textrm{IfThenElse}[\kappa ,\texttt{pred}(\widetilde{e'}),e_1,e_2]\) is a macro introduced for the convenience of stating the correspondence between the DM and HFL(Z) approaches in Section 3.4. It is expanded as follows.
$$\begin{aligned} &\textrm{IfThenElse}[\texttt {prop},\texttt{pred}(\widetilde{e'}),e_1,e_2] = (\texttt{pred}(\widetilde{e'})\wedge e_1) \vee (\lnot \texttt{pred}(\widetilde{e'}) \wedge e_2) \\ &\textrm{IfThenElse}[\eta \rightarrow \kappa ,\texttt{pred}(\widetilde{e'}),e_1,e_2] = \lambda x:\eta .\ \textrm{IfThenElse}[\kappa ,\texttt{pred}(\widetilde{e'}),e_1\;x,e_2\;x]. \end{aligned}$$
The return and bind expressions are encoded using the operators of the monad \(\texttt{W}\). In the case of the pure computation monad, the effect of (selective) CPS translation is achieved through \(\mathtt {return^{W}}\) and \(\mathtt {bind^{W}}\).

Example 4

Let us consider the function twice again as an example:
$$\begin{aligned} \lambda f: (\texttt {int}\rightarrow \texttt{M}\; \texttt {int}). \lambda x:\texttt {int}. \texttt {let~}y=f\;x\texttt {~in~}f\;y. \end{aligned}$$
It is translated to:
$$\begin{aligned} &\quad \,\,{(\lambda f: (\texttt {int}\rightarrow \texttt{M}\; \texttt {int}). \lambda x:\texttt {int}. \texttt {let~}y=f\;x\texttt {~in~}f\;y)}^{\textrm{HFL}} \\ &=\lambda f: \pi _{f}. \lambda x:\texttt {int}. \mathtt {bind^{W}}\;(f\;x)\;(\lambda y:\texttt {int}.{f\;y}). \end{aligned}$$
Here, \(\pi _{f}\) abbreviates \({(\texttt {int}\rightarrow \texttt{M}\; \texttt {int})}^{\textrm{WP}}\), which equals \(\texttt {int}\rightarrow \texttt{W}\;\texttt {int}\).    \(\square \)

Remark 1

Kobayashi et al. [9, 17] only sketched how to deal with side effects (other than “events” [17]) in an example-based manner. Thus, the general translation given above using effect monads is also a contribution of this paper.    \(\square \)

3.4 Formalization of the Correspondence

Now we are ready to formalize the correspondence between the two approaches. We first show that there is a type derivation in the Dijkstra monad approach corresponding to the program transformation of the HFL(Z) approach.

The type system of the Dijkstra monad approach allows freedom in the choice of types of function parameters and the use of conversion and quantification rules. For example, \(\lambda f: (\texttt {int}\rightarrow \texttt{M}\; \texttt {int}).\ \lambda x:\texttt {int}.\ f\;(x+1)\), which has type \((\texttt {int}\rightarrow \texttt{M}\; \texttt {int})\rightarrow \texttt {int}\rightarrow \texttt{M}\; \texttt {int}\) in the simple monadic type system, can be given any of the following types in the DM type system:
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figp_HTML.png

Dummy

The first type abstracts over the predicate transformer of \(f\), while the second and third types accept only function arguments having a specific predicate transformer. For example, a function of the first type can be applied to both \(\lambda y. \texttt {return}\ (y+10)\) and \(\lambda y. \texttt {return}\ y\), but a function of the second type can be applied to \(\lambda y. \texttt {return}\ (y+10)\) but not to \(\lambda y. \texttt {return}\ y\), and a function of the third type can be applied to \(\lambda x. \texttt {return}\ y\) but not to \(\lambda y. \texttt {return}\ (y+10)\). Since the output of the transformation by the HFL(Z) approach is \(\lambda f.\ \lambda x.\ f(x+1)\), deriving the first type
$$\begin{aligned} \forall w_{f}.\ f:(y\mathbin {:}\texttt {int}\rightarrow \texttt{D}\; \texttt {int}\; (w_{f}\;y)) \rightarrow x:\texttt {int}\rightarrow \texttt{D}\; \texttt {int}\; (w_{f}(x+1)) \end{aligned}$$
is the most natural choice in order to show the correspondence. Actually, this type is obtained as \(\operatorname {toDM}[(\texttt {int}\rightarrow \texttt{M}\; \texttt {int})\rightarrow \texttt {int}\rightarrow \texttt{M}\; \texttt {int}, \lambda f. \lambda x.\ f(x+1)]\), as confirmed by the following calculation.
$$\begin{aligned} &\operatorname {toDM}[(\texttt {int}\rightarrow \texttt{M}\; \texttt {int})\rightarrow \texttt {int}\rightarrow \texttt{M}\; \texttt {int}, \lambda f. \lambda x.\ f(x+1)]\\ =& \forall w_{f}. f\mathbin {:}\operatorname {toDM}[\texttt {int}\rightarrow \texttt{M}\; \texttt {int}, w_{f}]\rightarrow \operatorname {toDM}[\texttt {int}\rightarrow \texttt{M}\; \texttt {int}, (\lambda f. \lambda x.\ f(x+1))w_{f}]\\ =&\cdots \\ =& \forall w_{f}. f:(y\mathbin {:}\texttt {int}\rightarrow \texttt{D}\; \texttt {int}\; (w_{f}\;y)) \rightarrow x:\texttt {int}\rightarrow \texttt{D}\; \texttt {int}\; (w_{f}(x+1)). \end{aligned}$$
Here, we have omitted the type of \(w_{f}\). Actually, this observation can be generalized: when the parameter \(f\) has the simple type \(t\), we choose \(\operatorname {toDM}[t, w_{f}]\) as the DM type for \(f\), where \(w_{f}\) is universally quantified. Hereafter, we call \(\operatorname {toDM}[t, w_{f}]\) as the canonical DM type corresponding to the simple type \(t\) specified by the predicate transformer \(w_{f}\).
We also have to bridge the gap on the treatment of conditional expressions: \(\texttt {if~}\texttt{pred}{(\widetilde{e'})}\texttt {~then~}e_1\texttt {~else~}e_2\). The DM type system derives the predicate transformer of this expression as \(\texttt {if~}\texttt{pred}(\widetilde{e'})\texttt {~then~}w_1\texttt {~else~}w_2\), where \(w_1\) and \(w_2\) are predicate transformers of \(e_1\) and \(e_2\), respectively. On the other hand, HFL(Z) formulas do not include conditional expressions, so they must be encoded as formulas, using the macro \(\textrm{IfThenElse}[\texttt{W}\;a,\texttt{pred}(\widetilde{e'}),w_1,w_2]\). We bridge this gap by the translation \({w}^\dagger \), which replaces conditional expressions in DM predicate transformers with the corresponding formula using this macro, defined as follows7.
$$\begin{aligned}& {(\texttt {if~}\texttt{pred}(\widetilde{e})\texttt {~then~}w_1\texttt {~else~}w_2)}^\dagger := \textrm{IfThenElse}[\pi ,\texttt{pred}{(\widetilde{{e}^\dagger })},{w_1}^\dagger ,{w_2}^\dagger ] \\ & \quad (\text {when }\varGamma \mathrel {\vdash ^\textrm{DM}}\texttt {if~}\texttt{pred}(\widetilde{e})\texttt {~then~}w_1\texttt {~else~}w_2: \pi ) \end{aligned}$$
The following theorem formalizes the syntactic correspondence between the DM type system and the program transformation of the HFL(Z) approach.

Theorem 1

If \(\,\mathrel {\vdash }e: r\), then there exists a predicate transformer \(w\) such that \(\,\mathrel {\vdash ^\textrm{DM}}e: \operatorname {toDM}[r, w] \) and \({w}^\dagger = {e}^{\textrm{HFL}}\).

Example 5

Suppose the effect parameters are the ones for stateful computation given in Example 2. Let \(e\) be the expression:
$$\begin{aligned} \texttt {let~}x=\texttt{get}\texttt {()}\texttt {~in~}\texttt {if~}x>0\texttt {~then~}\texttt {return}\ x\texttt {~else~}\texttt {return}\ 0. \end{aligned}$$
It is transformed to the following HFL(Z) formula:
$$\begin{aligned} &{e}^{\textrm{HFL}} =\mathtt {bind^{W}}\;(w^{get} \texttt {()})\;(\lambda x.\textrm{IfThenElse}[\texttt{W}\;\texttt {int},x>0,\mathtt {return^{W}}\;x,\mathtt {return^{W}}\;0]) \\ &\ = \mathtt {bind^{W}}\;(w^{get} \texttt {()})\;(\lambda x.\lambda h. \lambda p. x>0 \wedge \mathtt {return^{W}}\;x\;h\;p \vee x\le 0 \wedge \mathtt {return^{W}}\;0\;h\;p)). \end{aligned}$$
Let \(w\) be the predicate transformer:
$$\begin{aligned} w:=\mathtt {bind^{W}}\;(w^{get} \texttt {()})\;(\lambda x.\texttt {if~}x>0\texttt {~then~}\mathtt {return^{W}}\;x\texttt {~else~}\mathtt {return^{W}}\;0). \end{aligned}$$
This \(w\) satisfies \(\mathrel {\vdash ^\textrm{DM}}e: \texttt{D}\; \texttt {int}\; w \) and \(w^\dagger = {e}^{\textrm{HFL}}\).    \(\square \)
To prove the theorem above, we introduce another transformation \({e}^\textrm{DMWP}\), which serves as a witness for \(w\) in the theorem. It is the same as \({(\cdot )}^{\textrm{HFL}}\), except the following case for conditional expressions, which are transformed into those of predicate transformers (because conditional expressions are available in the DM language for predicate transformers).
$$\begin{aligned}& {(\texttt {if~}\texttt{pred}(\widetilde{e'})\texttt {~then~}e_1\texttt {~else~}e_2)}^\textrm{DMWP} \\ &\quad := \texttt {if~}\texttt{pred}(\widetilde{{e'}^\textrm{DMWP}})\texttt {~then~}{e_1}^\textrm{DMWP}\texttt {~else~}{e_2}^\textrm{DMWP}\\ &\qquad (\text {when } \varGamma \mathrel {\vdash }\texttt {if~}\texttt{pred}(\widetilde{e'})\texttt {~then~}e_1\texttt {~else~}e_2: \texttt{M}\; a) \end{aligned}$$
Next, we show that \({e}^\textrm{DMWP}\) is indeed a witness for \(w\) in the theorem. To show that \(w={e}^\textrm{DMWP}\) implies \(\,\mathrel {\vdash ^\textrm{DM}}e: \operatorname {toDM}[r, w] \), we need to generalize this property to open terms. To that end, we define the “canonical” type enviroment of the DM type system that corresponds to a type environment of the simple monadic type system, as follows.
$$\begin{aligned} & {\emptyset }^{\textrm{DM}} := \emptyset \qquad {(\varGamma , x\mathbin {:}a)}^{\textrm{DM}} := {\varGamma }^{\textrm{DM}}, x\mathbin {:}a\\ {(\varGamma , x\mathbin {:}t)}^{\textrm{DM}} &:= {\varGamma }^{\textrm{DM}}, w_{x}\mathbin {:}{t}^{\textrm{WP}}, x\mathbin {:}\operatorname {toDM}[t, w_{x}] \qquad (\text {where }w_{x}\text {: fresh}) \end{aligned}$$
Here, variables of a base type \(a\) are kept as they are, while a variable of type \(t\) is replaced by a pair of \(w_{x}\) and \(x\), where \(x\) has the canonical type specified by \(w_{x}\).

To deal with open terms, we also need to rename variables. This is due to the observation that the DM type abstracts over the behavior of functional parameters using universal quantification of its predicate transformer, while the result of the transformation \({(\cdot )}^\textrm{DMWP}\) takes the translated higher-order predicate as an argument. For example, the function \(\lambda f: (\texttt {int}\rightarrow \texttt{M}\; \texttt {int}).\ f\;0\) can be given the type https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figq_HTML.gif while the program transformation yields https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figr_HTML.gif . As indicated by the red color, https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figs_HTML.gif in https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figt_HTML.gif actually corresponds to https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figu_HTML.gif in the DM type, rather than \(f\). Based on this observation, we rename \(f\) to \(w_{f}\), by using the substitution \(\sigma _\varGamma \), defined by: \(\sigma _\varGamma (x) = w_{x}\) if \(\varGamma (x)\) contains \(\texttt{M}\) and \(\sigma _\varGamma (x) = x\) otherwise.

Now, the correspondence between the typing rules of DM and the program transformation \({(\cdot )}^\textrm{DMWP}\) on open terms can be stated as the following lemma.

Lemma 1

  1. 1.

    If \(\varGamma \mathrel {\vdash }e: a\), then \({\varGamma }^{\textrm{DM}} \mathrel {\vdash ^\textrm{DM}}e: a\) and \(\sigma _\varGamma ({e}^\textrm{DMWP}) = e\).

     
  2. 2.

    If \(\varGamma \mathrel {\vdash }e: \texttt{M}\; a\), then \({\varGamma }^{\textrm{DM}} \mathrel {\vdash ^\textrm{DM}}e: \texttt{D}\; a\; \sigma _\varGamma ({e}^\textrm{DMWP}) \), and \({\varGamma }^{\textrm{DM}} \mathrel {\vdash ^\textrm{DM}}\sigma _\varGamma ({e}^\textrm{DMWP}) : \texttt{W}\;a\).

     
  3. 3.

    If \(\varGamma \mathrel {\vdash }e: t\), then \({\varGamma }^{\textrm{DM}} \mathrel {\vdash ^\textrm{DM}}e: \operatorname {toDM}[t, \sigma _\varGamma ({e}^\textrm{DMWP})] \) and \({\varGamma }^{\textrm{DM}} \mathrel {\vdash ^\textrm{DM}}\sigma _\varGamma ({e}^\textrm{DMWP}) : {t}^{\textrm{WP}}\).

     

The proof is found in the longer version [19]. As a special case of the lemma above, when the type environment \(\varGamma \) is empty, we have \(\,\mathrel {\vdash ^\textrm{DM}}e: \operatorname {toDM}[r, w] \) for \(w= {e}^\textrm{DMWP}\), confirming that \(w={e}^\textrm{DMWP}\) indeed serves as a witness for the condition \(\,\mathrel {\vdash ^\textrm{DM}}e: \operatorname {toDM}[r, w] \) of Theorem 1.

It remains to show \(w={e}^\textrm{DMWP}\) also satisfies \({w}^\dagger = {e}^{\textrm{HFL}}\), the second condition of Theorem 1, as stated in the following lemma.

Lemma 2

If \(\varGamma \mathrel {\vdash }e: r\), then \({{e}^\textrm{DMWP}}^\dagger ={e}^{\textrm{HFL}}\).

We now obtain Theorem 1 as a corollary of the two lemmas above.

Proof

(Theorem 1). Let \(w = {e}^\textrm{DMWP}\). Then by the special cases of Lemmas 1 and 2 where \(\varGamma \) is empty, we have \(\,\mathrel {\vdash ^\textrm{DM}}e: \operatorname {toDM}[r, w] \) and \({w}^\dagger = {e}^{\textrm{HFL}}\).

We now show the converse, i.e., that for every predicate transformer derived in the DM approach, an at least equally good predicate transformer can be obtained in the HFL(Z) approach. We assume that the DM type system is sound with respect to the predicate transformer semantics (as defined in Section 3.3 by the transformation to HFL(Z) formulas), i.e., if \(\mathrel {\vdash ^\textrm{DM}}e: \texttt{D}\; a\; w\) holds, then \(w\) is a sound predicate transformer (even though it may not be the weakest one). Then the following theorem follows immediately from the assumption.

Theorem 2

Suppose \(\vdash e: \texttt{M}\; a\) and \(\mathrel {\vdash ^\textrm{DM}}e:\texttt{D}\; a\; w\). Then for all \(\widetilde{v}\) such that \(\vdash w\,\widetilde{v}:\texttt {prop}\), \(w\;\widetilde{v}\) implies \({e}^{\textrm{HFL}}\ \widetilde{v}\). In particular, in the case of the pure computation monad, \(w\,p\) implies \({e}^{\textrm{HFL}}\ p\) for every predicate \(p\) (that represents the condition on the value returned by \(e\)) of type \(a\rightarrow \texttt {prop}\).

4 Differences between the Two Approaches

In the previous section, we have shown a direct correspondence between the Dijkstra monad approach and the HFL(Z) approach for a restricted fragment of the languages supported by the two approaches. In this section, we discuss what differences arise without the restrictions. The major restrictions on the language considered in the previous section are: (1) Restriction on the inner type of the monad. The type \(a\) inside the monad \(\texttt{M}\; a\) is restricted to \(\texttt {unit}\) or \(\texttt {int}\), and not (pure) functional types such as \(\texttt {int}\rightarrow \texttt {int}\). (2) Restriction to non-nested monads. Nested monadic types like \(\texttt {int}\rightarrow \texttt{M}\; (\texttt {int}\rightarrow \texttt{M}\; \texttt {unit})\) were excluded. (3) Restriction to non-recursive programs. Of the three kinds of restrictions, the first one is not fundamental. The restriction comes from the restriction on the side of HFL(Z). In HFL(Z), the final return type of a function type must be \(\texttt {prop}\) (i.e., the types of functions or predicates are restricted to the form \(\tau _1\rightarrow \cdots \rightarrow \tau _k\rightarrow \texttt {prop}\), and types like \(\texttt {int}\rightarrow \texttt {int}\) are disallowed). That restriction is required in HFL(Z) to ensure that the least/greatest fixpoints always exist. A workaround is to wrap the final return type with a monad, such as using \(\texttt {int}\rightarrow \texttt{M}\; \texttt {int}\) instead of \(\texttt {int}\rightarrow \texttt {int}\). In addition, if we allow only terminating functions by employing a mechanism for checking the termination of functions as in F\(^\star \) , then there would be no problem in removing the restriction on the HFL(Z) side.

The second and third restrictions are more fundamental, revealing important differences between the two approaches, as discussed below.

4.1 On Nested Monads

To see the difference between the two approaches in the presence of nested monads, let us consider the following program:
https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Figv_HTML.png

Dummy

Here, increment is as defined in Section 2. In the DM approach, the function nest is given the type \(\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\; (\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\;\texttt {unit}\;w_2)\;w_1\) where \(w_1=\lambda h. \lambda p.p\,\texttt {increment}\, (h+2)\) and \(w_2=\lambda h. \lambda p.p\,\texttt {()}\,(h+1)\). Note that the information about the behavior of the function is split into \(w_1\) and \(w_2\), where \(w_1\) and \(w_2\) respectively express the effect when the first and second arguments are given. Note also that the code increment is embedded in the former predicate transformer \(w_1\).
The function client is given the type \(\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\; \texttt {unit}\;w\), where \(w\) is calculated as follows (using the \(\beta \)-equality).
$$\begin{aligned} w &= \lambda h. \lambda p. w_1\,h\,(\lambda x.\lambda h'.w_2\,h'\,p)\\ &= \lambda h. \lambda p. (\lambda h. \lambda p.p\,\texttt {increment}\, (h+2))\,h\,(\lambda x.\lambda h'.w_2\,h'\,p)\\ &= \lambda h. \lambda p. (\lambda x.\lambda h'.w_2\,h'\,p)\,\texttt {increment}\,(h+2)\\ &= \lambda h. \lambda p. w_2\,(h+2)\,p = \lambda h. \lambda p. p\,\texttt {()}\,(h+3). \end{aligned}$$
Thus, the code increment is actually irrelevant in the computation of the predicate transformer.
The shape of the formula obtained in the HFL(Z) approach is quite different. By applying the transformation in Section 3.3 to the body of the function \(\texttt {nest}\), we obtain:
$$\begin{aligned} \varphi _{\texttt {nest}} &:= \mathtt {bind^{W}}\;(\varphi _{\texttt {inc}}(\,))\;\lambda (\,).(\mathtt {bind^{W}}\;(\varphi _{\texttt {inc}}(\,))\;\lambda (\,).\mathtt {return^{W}}\;\,\varphi _{\texttt {inc}})\\ &= \lambda h.\lambda p.p\,\varphi _{\texttt {inc}}\, (h+2). \end{aligned}$$
where \(\varphi _{\texttt {inc}} = \lambda (\,).\lambda h.\lambda p.p((\,),h+1)\), and \(\mathtt {bind^{W}} \) and \(\mathtt {return^{W}} \) are as given in Section 3.1, Example 2. Thus, the behavior of the function is described in the single predicate \(\varphi _{\texttt {nest}}\), unlike in the DM approach (where the same information is split into \(w_1\) and \(w_2\)). For the client code (whose type does not contain nested monads), however, we obtain the same formula as \(w\):
$$\begin{aligned} \varphi _{\texttt {client}} &:= \mathtt {bind^{W}}\;\varphi _{\texttt {nest}}\;\lambda f.f()\\ &= \lambda h.\lambda p.\varphi _{\texttt {nest}}\;h\;(\lambda (x,h').(\lambda f.f())\;x\;h'\;p) =_\beta \lambda h.\lambda p.p\,\texttt {()}\, (h+3). \end{aligned}$$
Interestingly, the formula \(\varphi _{\texttt {nest}} = \lambda h.\lambda p.p\,\varphi _{\texttt {inc}}\,(h+2)\) looks quite similar to \(w_1 = \lambda h.\lambda p.p\,\texttt {increment}\, (h+2)\). The only difference is that increment in \(w_1\) is code, while \(\varphi _{\texttt {inc}}\) in \(\varphi _{\texttt {nest}}\) is a specification (in the form of a predicate transformer) of the code. Both look even more similar if we replace \(w_1\) with the following predicate transformer \(w_1'\):(\(^{8}\) Universal quantifications inside predicate transformers are not supported by the fragment defined in Section 3, but are supported by the original type system of Dijkstra monads [15].)
$$ w_1' = \lambda h.\lambda p.\forall f: (\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\;\texttt {unit}\;w_2). p\,f\,(h+2).^{8} $$
Here, by using the quantification over the code, we avoid embedding the code increment and instead provide its specification as a type. Thus, \(w_1'\) contains the same amount of information as \(\varphi _{\texttt {nest}}\). A difference between the two approaches still remains, however. Even with this modification, \(f\) and its type are actually irrelevant in the computation of the predicate transformer for client, just as increment has been ignored in the computation of \(w\). Information about the behavior of nest for the second argument is duplicated in the DM approach, no matter whether we use the type \(\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\; (\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\;\texttt {unit}\;w_2)\;w_1\) or \(\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\; (\texttt {unit}\rightarrow \texttt{D}^{\texttt{STATE}}\;\texttt {unit}\;w_2)\;w_1'\). (In the former type, \(w_1\) contains the code increment, whose behavior is already described in \(w_2\), and in the latter type, \(w_2\) also occurs in \(w_1'\).) This problem can be annoying for a programmer and can incur unnecessary annotation overheads, especially when the nesting depth of monads is larger, like \(\texttt {int}\rightarrow \texttt{M}\; (\texttt {int}\rightarrow \texttt{M}\; (\texttt {int}\rightarrow \texttt{M}\; (\texttt {int}\rightarrow \texttt{M}\; \texttt {unit})))\). We, therefore, think that the HFL(Z) approach is preferable in this aspect.

4.2 On Recursive Programs

For recursive programs, there are clear differences between the two approaches. In the DM approach, each recursive function must be annotated with an appropriate type (which includes the predicate transformer) by a programmer, just like invariants for loops and recursive functions must be provided in deductive verification. The obtained predicate transformer is not necessarily the weakest one, depending on the precision of the annotations. In the HFL(Z) approach, no annotations are required for recursive functions, and (a predicate corresponding to) the weakest predicate transformer can be automatically expressed by using fixpoint operators.

As an example, let us consider the following program (which is admittedly artificial, for the sake of simplicity).
$$\begin{aligned}& \texttt {let\ rec}\ \texttt {add}\;x\;y = \texttt {if~}x\le 0\texttt {~then~}y\texttt {~else~}\texttt {add}\;(x-1)\;(y+1) \\ & \texttt {let\ rec}\ \texttt {zero}\;x = \texttt {if~}x\le 0\texttt {~then~}0\texttt {~else~}\texttt {zero}\;(x-1)\\ & \texttt {let\ main}\;x=\texttt {assert}\;(\texttt {add}\;x\;(\texttt {zero}\;x) \ge x) \end{aligned}$$
It first defines recursive functions add and zero, and then asserts that \(\texttt {add}\;x\;(\texttt {zero}\;x) \ge x\) holds for every \(x\) in the main function. Suppose that we wish to verify that the assertion never fails. In the DM approach, we have to declare the following types:
$$\begin{aligned} & \texttt {add}: x\mathbin {:}\texttt {int}\rightarrow y\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(\lambda p.\forall r\ge x+y. p\;r)\\ & \texttt {zero}: x\mathbin {:}\texttt {int}\rightarrow \texttt{D}\;\texttt {int}\;(\lambda p.p\;0). \end{aligned}$$
Then the verification problem boils down to a type checking problem, which is solved by utilizing an underlying SMT solver. One may argue that it is a good programming practice to declare types, but it would be cumbersome to declare the types of all the recursive functions, including those of auxiliary functions.
In the HFL(Z) approach, the function main is automatically converted to the formula \( \varphi _{\texttt {main}} := \lambda x.\lambda p.P_{\texttt{Zero}}\;x\;(\lambda y.P_{\texttt{Add}}\;x\;y\;(\lambda z.z\ge x)) \) where the predicates \(P_{\texttt{Add}}\) and \(P_{\texttt{Zero}}\) are defined by:
$$\begin{aligned} &P_{\texttt{Add}}\;x\;y\;p =_\nu (x\le 0\Rightarrow p\;y) \wedge (x>0 \Rightarrow P_{\texttt{Add}}\;(x-1)\;(y+1)\;p)\\ &P_{\texttt{Zero}}\;x\;p =_\nu (x\le 0\Rightarrow p\;0) \wedge (x>0 \Rightarrow P_{\texttt{Zero}}\;(x-1)\;p). \end{aligned}$$
Its validity can automatically be proved by an HFL(Z) validity checker [6, 8]. Thus, the whole verification process is fully automatic in the HFL(Z) approach.

The property above is a safety property, but one can mix the least and greatest fixpoint equations to express arbitrary regular temporal properties [9, 17], and its validity can be automatically checked (though the backend HFL(Z) validity checker is necessarily incomplete) [8].

Despite the advantages mentioned above, the HFL(Z) approach has also a disadvantage: automated HFL(Z) validity checkers used in the approach are much less efficient and less predictable than SMT solvers used in the DM approach. The inefficiency and unpredictability are inevitable because HFL(Z) validity checking is undecidable in general. In contrast, the DM approach uses the annotations to reduce the validity checking problem to simpler problems that can be discharged by ordinary SMT solvers. In fact, there are programs that the HFL(Z) approach cannot verify in realistic time, but given appropriate annotations, the DM approach can verify; see Section 5.2 for such an example. The DM approach allows modular verification; by using type annotations, the whole verification problem can be decomposed into small pieces of verification conditions.

In summary, the two approaches handle recursion differently, resulting in contrasting advantages and disadvantages:
  • In the DM approach, users need to provide a type annotation (including predicate transformers) for each recursive function, whereas in the HFL(Z) approach, there is no such need. The HFL(Z) approach can deal with not only partial/total correctness but also arbitrary regular temporal properties.

  • The DM approach requires only an ordinary SMT solver as a backend, which is more efficient and predictable than a higher fixpoint logic solver required in the HFL(Z) approach. The former is thus more suitable for interactive program development.

  • The DM approach admits modular verification, and thus it is more scalable to large programs than the HFL(Z) approach, by taking advantage of the annotations given by users.

5 Semi-Automated Program Verification Using \(\nu \)HFL(Z)

Based on the observations of the similarities and differences between the DM and HFL(Z) approaches, we introduce a semi-automated verification method to take the best of two approaches in this section.

5.1 Overview of Our Approach

Our method to integrate the DM and HFL(Z) approaches is to allow partial annotations of recursive functions using (a restricted form of) DM types, and then reduce the verification problem to the refinement type checking problem for \(\nu \)HFL(Z) [6]. Let us reconsider the example in Section 4.2. Recall that type annotations have to be provided for both \(\texttt {add}\) and \(\texttt {zero}\) in the DM approach, and none are provided in the HFL(Z) approach. In our new approach, we allow a user to provide type annotations partially; for example, a user may choose not to annotate zero, but to annotate add as follows, asserting that \(\texttt {add}\;x\;y\) returns \(x+y:\)
$$\begin{aligned} & \texttt {add}: x\mathbin {:}\texttt {int}\rightarrow y\mathbin {:}\texttt {int}\rightarrow \texttt{H}^{\texttt {pure}}\;{\texttt {int}}\;{\textsf{true}}\;{\lambda r.r\ge x+y}. \end{aligned}$$
Here, the type constructor \(\texttt{H}^{\texttt {pure}}\) is a kind of Hoare monad [13], defined by:
$$\begin{aligned} & \texttt{H}^{\texttt {pure}}\;a\;\textit{pre}\;\textit{post} := \texttt{D}\;a\;(\lambda p.\textit{pre} \wedge \forall r. \textit{post}\,r \Rightarrow p). \end{aligned}$$
(This encoding follows [15], Section 3.1.)
Given such a partially annotated program, we can convert it to a HFL(Z) formula as in Section 3.3, and, additionally, translate the type annotations for recursive functions to refinement types for the corresponding HFL(Z) predicates [6, 7]. In the above case, the type annotation for add can be converted to the following type annotation for the corresponding HFL(Z) predicate:
$$\begin{aligned} P_{\texttt{Add}}: (x\mathbin {:}\texttt {int}\rightarrow y\mathbin {:}\texttt {int}\rightarrow (r\mathbin {:}\texttt {int}\rightarrow \bullet [r \ge x+y])\rightarrow \bullet [\textsf{true}]). \end{aligned}$$
Here, \(\bullet [\theta ]\) denotes the type of a proposition that holds whenever \(\theta \) is true. Thus, the type above means that \(P_{\texttt{Add}}\;x\;y\;p\) is true if \(p(x+y)\) is true. In general, the type \(\texttt{H}^{\texttt {pure}}\;a\; pre \; post \) for a term in the source language can be automatically converted to \((r\mathbin {:}a \rightarrow \bullet [ post \;r])\rightarrow \bullet [ pre ]\) for the corresponding HFL(Z) formula. We can then decompose the verification problem to type checking problems for \(\nu \)HFL(Z) (which is a fragment of HFL(Z) with only greatest fixpoint operators) [6, 7].

In general, given (i) a main formula \(\varphi \), (ii) a set \(D_1\cup D_2\) of fixpoint equations on \(\nu \)HFL(Z) predicates, and (iii) type environments \(\varDelta _1\) for \(D_1\) (which are automatically derived from type annotations), we can reduce the validity of \(\varphi \) to the following type checking problems, thanks to the soundness of the refinement type system for \(\nu \)HFL(Z) [6, 7].

  • Are the type annotations valid, i.e., does there exist a type environment \(\varDelta _2\) for \( D_2 \), such that \(\varDelta _1,\varDelta _2\vdash ^{\texttt {RT}}{\varphi _P} :\rho \) for each type binding \(P:\rho \) in \(\varDelta _1,\varDelta _2\)?

  • Is the main formula valid, i.e., does there exist a type environment \(\varDelta _2\) for \( D_2 \), such that \(\varDelta _1,\varDelta _2\vdash ^{\texttt {RT}}\varphi :\bullet [\textsf{true}]\) and \(\varDelta _1, \varDelta _2 \vdash ^{\texttt {RT}}{\varphi _P} :\rho \) for each type binding \(P:\rho \) in \(\varDelta _2\)?

Here, \(\varDelta \vdash ^{\texttt {RT}}\varphi :\rho \) is the type judgment relation as defined in [6] (whose details are not important here) and \( \varphi _P \) is the body of \( P \). Thus, partial type annotations enable modular verification, which is expected to be more scalable and predictable than the original HFL(Z) approach. In the above decomposition of the verification problem, the weakest predicate transformers are automatically computed and used for unannotated functions, thanks to the expressive power of HFL(Z). (In contrast, the implementation of DM in F* does seem to allow some type annotations to be omitted, but in that case, very imprecise predicate transformers would be used for unannotated recursive functions.)

5.2 Experiments

We extended the existing type-based \(\nu \)HFL(Z) validity checker, ReTHFL [6] to allow partial type annotations, and conducted experiments to confirm the effectiveness of our approach.8 Currently, the type annotations need to be provided for \(\nu \)HFL(Z) formulas rather than for source programs. As discussed in Section 5.1, it is easy to automatically convert the Hoare-monad-style type annotations for source programs (which may be considered a restricted form of DM-style type annotations) to type annotations for \(\nu \)HFL(Z) formulas; it is left for future work to implement such a converter to make annotations easier.

In the experiment we used a Linux server equipped with Intel Xeon Gold 6242 Processor @ 2.80 GHz, with 16 cores, and 64 GB of RAM. For each (sub-)problem, we ran each tool five times and calculated the average execution time. Since the performance of the validity checker depends strongly on the performance of the underlying Constrained Horn Clause (CHC) solver, we used both Z3/Spacer [10] and HoIce [3] as underlying CHC solvers and show only the best of their results for each problem; in other words, the reported verification times indicate those in case the backend solvers Z3/Spacer [10] and HoIce [3] are called in parallel.

As the verification target of experiments, we gathered, from a benchmark for higher-order functional program verification9, programs for which the current ReTHFL is slow or timeouts.10 The LOCs of the target programs varies from 10 to 153. For each program, we provided a refinement type as an annotation for one of the recursive predicates (which corresponds to one of the recursive functions in the source program). See the longer version [19] for mode details about the benchmark programs and an example of type anntoations.
Table 2.

Comparison between the original fully automated method by Katsura et al. [6] and our semi-automated method (timeout = 300 seconds)

https://static-content.springer.com/image/chp%3A10.1007%2F978-3-031-91121-7_16/MediaObjects/652625_1_En_16_Tab2_HTML.png

Dummy

The experimental results are shown in Table 2. In the table, we show the sizes of programs and annotations in terms of bytes for a fair comparison between the sizes of programs and annotations (as it is possible to write a type annotation in one long line). The first row of each program shows the result of the original version of ReTHFL. As it is a fully automated checker, no annotations were provided. The rest of the rows show the results of our method. The “Method” column shows the name of the function annotated and the annotation size. In the rest of the columns, “X / Y” means that X is the result of the sub-problem (a) that checks the correctness of the annotation and Y is the result of the sub-problem (b) that checks the validity of the whole formula.

For all runs of each sub-problem, our tool successfully checked the validity and terminated within a few seconds. This confirms the effectiveness of our approach and the behavior of the tool is much more predictable than the fully-automated verification using the original ReTHFL. We think the size of annotations is acceptable; actually, the annotation size would become smaller, if we allow a user to provide annotations for source programs rather than for HFL(Z) formulas.

The results also support the advantage of our method over the DM approach in reducing the annotation burdens. While the DM approach requires an annotation for every recursive function, our method succeded given only one annotated function for each program.

The Dijkstra Monad Approach and the HFL(Z) Approach. The framework of Dijkstra monads was first introduced by Swamy et al. [15] as a general method for the verification of higher-order programs with monadic effects. To define a Dijkstra monad that is consistent with the semantics of the effect, Ahman et al. [1] showed that CPS transformation of the monadic model of the effect yields such a correct monad. This result is closely related to our observation of the similarity with the HFL(Z) approach. As we mentioned in Section 3.4, Lemma 1, which is part of the proof of the correspondence theorem, generalizes Theorem 6 of Ahman et al. to be parameterized by effect parameters. A successive study by Maillard et al. [12] reinterpreted and extended the framework by considering a Dijkstra monad as two monads—a computational monad and a specification monad—related by a monad morphism.

The HFL(Z) approach was proposed by Kobayashi et al. [9, 17], which supports the verification of temporal properties of higher-order programs with general recursion. Watanabe et al. [17] extended the approach to the verification of arbitrary regular linear/branching-time properties expressible in alternating parity tree automata. Following those studies, fully automated HFL(Z) validity checkers have been developed [58], which, in combination with the above-mentioned reductions [9, 17], yielded fully automated verification tools for higher-order functional programs. As discussed already, while the fully automated approach is attractive for verification of small programs, it suffers from (non-)scalability and unpredictability issues for large programs.

Most of the previous studies on Dijkstra monads have considered only partial or total correctness, while the HFL(Z) approach supports a wide range of temporal properties of possibly divergent programs. An exception is the work of Silver and Zdancewic [14], which can deal with termination-sensitive properties of possibly divergent programs with monadic effects. The behavior of such programs is expressed using interaction trees [18], a coinductive data structure with effects as internal nodes and resulting values as leaves. The idea of expressing computations as possibly infinite trees is shared with the work of Watanabe et al. [17] in the HFL(Z) approach, where the behavior of a possibly non-terminating program is modeled by a possibly infinite tree of (abstract) events. It is left for future work to extend our result in Section 3 to establish a precise relationship between those studies.

Predicate Transformers for Higher-Order Programs. Kura [11] considered a general categorical framework for a higher-order language with effects and recursion, and showed that the (semantically defined) weakest precondition transformer of a program can be obtained by the CPS transformation. He has shown that a fragment of the HFL(Z) approach [9] can be understood as an instance of his result. He did not show, however, a direct correspondence between the DM and HFL(Z) approaches. Another related approach to higher-order program verification based on predicate transformers is Charguéraud’s work [4]. He showed a construction of a characteristic formula \([\![t]\!]\) for a term \(t\), such that \([\![t]\!]\,P\) is valid if and only if \(t\) satisfies the postcondition \(P\). Thus, \([\![t]\!]\) can be viewed as a predicate transformer. Charguéraud’s approach is motivated by interactive verification using a proof assistant, while the HFL(Z) approach is motivated by automated verification. This difference in the motivations seems to have led to the difference in the choice of logics. Charguéraud used a more expressive higher-order logic (like that of Coq) than HFL(Z) to express characteristic formulae so that the characteristic formula \([\![t]\!]\) has almost the same shape as the original term \(t\). In contrast, HFL(Z) is designed to be a minimal higher-order logic for ease of automation.

7 Conclusion

This paper studied the relationship between two approaches to higher-order program verification: the Dijkstra monad approach and the HFL(Z) approach. We showed the correspondence between them in a restricted setting, and discussed the differences between the two approaches outside the restriction. Although the two approaches are closely related in the restricted fragment, the full systems have some different strengths. Based on this observation, we proposed a semi-automated verification method and experimentally confirmed that our method can verify programs that cannot be verified by the existing HFL(Z)-based automated tool, with fewer annotations than in the DM approach. A full integration between the two approaches is left for future work.

Acknowledgment

We would like to thank anonymous referees for useful comments.

This work was supported by JSPS KAKENHI Numbers Grant JP20H05703 and JP24K20731.

Open Access This chapter is licensed under the terms of the Creative Commons Attribution 4.0 International License (http://​creativecommons.​org/​licenses/​by/​4.​0/​), which permits use, sharing, adaptation, distribution and reproduction in any medium or format, as long as you give appropriate credit to the original author(s) and the source, provide a link to the Creative Commons license and indicate if changes were made.

The images or other third party material in this chapter are included in the chapter's Creative Commons license, unless indicated otherwise in a credit line to the material. If material is not included in the chapter's Creative Commons license and your intended use is not permitted by statutory regulation or exceeds the permitted use, you will need to obtain permission directly from the copyright holder.

Fußnoten
1

The Dijkstra monad is used to handle effectful programs, but we consider here a pure functional program for the sake of simplicity; see later sections for more details about the Dijkstra monad.

 
2

In this particular case, we can obtain the same condition by just unfolding the definition of \(\texttt {succ}\) in \(\forall x.x>0\Rightarrow \texttt {succ}\ x>0\). It does not work, however, in general, when the function is recursively defined or when its code is unavailable and only the type is known.

 
3

In the case of a stateful program, one should also apply a store-passing transformation first, to obtain a pure functional program. Such examples are given later.

 
4

This slight asymmetry of the correspondences is due to the nondeterminism of type derivations in the DM approach.

 
5

Usually, \(\texttt{W}\;a\) is defined as \(S \rightarrow (a\times S \rightarrow \texttt {prop}) \rightarrow \texttt {prop}\) for a store type \(S\). To make the language minimum, however we avoid using pairs by using the currying transformation.

 
6

Here, the actual translation should take a type judgment. For the sake of simplicity, we refer to the type environment and the type of an expression only where necessary.

 
7

For other cases, we just compositionally apply the translation. For example, \({(e_1 e_2)}^\dagger := {e_1}^\dagger \; {e_2}^\dagger \). Note that this translation is not always guaranteed to yield well-formed HFL(Z) formulas. Nevertheless, this is not a problem for the correspondence formalized as Theorem 1, as this theorem claims the existence of a DM predicate transformer that corresponds to a HFL(Z) formula via this translation.

 
8

An artifact containing our tool and a script to reproduce the experiment is publicly available at https://​doi.​org/​10.​5281/​zenodo.​14614021.

 
10

There are a few other programs in the benchmark that ReTHFL cannot verify, but they are due to a certain theoretical limitation of the underlying refinement type system of ReTHFL, which cannot be remedied by type annotations.

 
Literatur
  1. Ahman, D., Hritcu, C., Maillard, K., Martínez, G., Plotkin, G.D., Protzenko, J., Rastogi, A., Swamy, N.: Dijkstra monads for free. In: Castagna, G., Gordon, A.D. (eds.) Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages, POPL 2017, Paris, France, January 18-20, 2017. pp. 515–529. ACM (2017), https://​doi.​org/​10.​1145/​3009837.​3009878
  2. Bhargavan, K., Bond, B., Delignat-Lavaud, A., Fournet, C., Hawblitzel, C., Hritcu, C., Ishtiaq, S., Kohlweiss, M., Leino, K.R.M., Lorch, J.R., Maillard, K., Pan, J., Parno, B., Protzenko, J., Ramananandro, T., Rane, A., Rastogi, A., Swamy, N., Thompson, L., Wang, P., Béguelin, S.Z., Zinzindohoue, J.K.: Everest: Towards a verified, drop-in replacement of HTTPS. In: Lerner, B.S., Bodík, R., Krishnamurthi, S. (eds.) 2nd Summit on Advances in Programming Languages, SNAPL 2017, May 7-10, 2017, Asilomar, CA, USA. LIPIcs, vol. 71, pp. 1:1–1:12. Schloss Dagstuhl - Leibniz-Zentrum für Informatik (2017), https://​doi.​org/​10.​4230/​LIPIcs.​SNAPL.​2017.​1
  3. Champion, A., Chiba, T., Kobayashi, N., Sato, R.: ICE-based refinement type discovery for higher-order functional programs. J. Autom. Reason. 64(7), 1393–1418 (2020), https://​doi.​org/​10.​1007/​s10817-020-09571-y
  4. Charguéraud, A.: Program verification through characteristic formulae. In: Hudak, P., Weirich, S. (eds.) Proceeding of the 15th ACM SIGPLAN international conference on Functional programming, ICFP 2010, Baltimore, Maryland, USA, September 27-29, 2010. pp. 321–332. ACM (2010), https://​doi.​org/​10.​1145/​1863543.​1863590
  5. Iwayama, N., Kobayashi, N., Suzuki, R., Tsukada, T.: Predicate abstraction and CEGAR for \(\nu \)HFL\(_z\) validity checking. In: Pichardie, D., Sighireanu, M. (eds.) Static Analysis - 27th International Symposium, SAS 2020, Virtual Event, November 18-20, 2020, Proceedings. Lecture Notes in Computer Science, vol. 12389, pp. 134–155. Springer (2020), https://​doi.​org/​10.​1007/​978-3-030-65474-0_​7
  6. Katsura, H., Iwayama, N., Kobayashi, N., Tsukada, T.: A new refinement type system for automated \(\nu \)HFL\(_z\) validity checking. In: d. S. Oliveira, B.C. (ed.) Programming Languages and Systems - 18th Asian Symposium, APLAS 2020, Fukuoka, Japan, November 30 - December 2, 2020, Proceedings. Lecture Notes in Computer Science, vol. 12470, pp. 86–104. Springer (2020), https://​doi.​org/​10.​1007/​978-3-030-64437-6_​5
  7. Katsura, H., Kobayashi, N., Sato, R.: Higher-order property-directed reachability. Proc. ACM Program. Lang. 7(ICFP) (aug 2023), https://​doi.​org/​10.​1145/​3607831
  8. Kobayashi, N., Tanahashi, K., Sato, R., Tsukada, T.: HFL(Z) validity checking for automated program verification. Proc. ACM Program. Lang. 7(POPL), 154–184 (2023), https://​doi.​org/​10.​1145/​3571199
  9. Kobayashi, N., Tsukada, T., Watanabe, K.: Higher-order program verification via HFL model checking. In: Ahmed, A. (ed.) Programming Languages and Systems - 27th European Symposium on Programming, ESOP 2018, Held as Part of the European Joint Conferences on Theory and Practice of Software, ETAPS 2018, Thessaloniki, Greece, April 14-20, 2018, Proceedings. Lecture Notes in Computer Science, vol. 10801, pp. 711–738. Springer (2018), https://​doi.​org/​10.​1007/​978-3-319-89884-1_​25
  10. Komuravelli, A., Gurfinkel, A., Chaki, S., Clarke, E.M.: Automatic abstraction in SMT-based unbounded software model checking. In: Sharygina, N., Veith, H. (eds.) Computer Aided Verification - 25th International Conference, CAV 2013, Saint Petersburg, Russia, July 13-19, 2013. Proceedings. Lecture Notes in Computer Science, vol. 8044, pp. 846–862. Springer (2013). https://​doi.​org/​10.​1007/​978-3-642-39799-8_​59
  11. Kura, S.: Higher-order weakest precondition transformers via a CPS transformation (2023), https://​doi.​org/​10.​48550/​arXiv.​2301.​09997
  12. Maillard, K., Ahman, D., Atkey, R., Martínez, G., Hritcu, C., Rivas, E., Tanter, É.: Dijkstra monads for all. Proc. ACM Program. Lang. 3(ICFP), 104:1–104:29 (2019), https://​doi.​org/​10.​1145/​3341708
  13. Nanevski, A., Morrisett, J.G., Birkedal, L.: Hoare type theory, polymorphism and separation. J. Funct. Program. 18(5-6), 865–911 (2008), https://​doi.​org/​10.​1017/​S095679680800695​3
  14. Silver, L., Zdancewic, S.: Dijkstra monads forever: termination-sensitive specifications for interaction trees. Proc. ACM Program. Lang. 5(POPL), 1–28 (2021), https://​doi.​org/​10.​1145/​3434307
  15. Swamy, N., Weinberger, J., Schlesinger, C., Chen, J., Livshits, B.: Verifying higher-order programs with the Dijkstra monad. In: Boehm, H., Flanagan, C. (eds.) ACM SIGPLAN Conference on Programming Language Design and Implementation, PLDI ’13, Seattle, WA, USA, June 16-19, 2013. pp. 387–398. ACM (2013), https://​doi.​org/​10.​1145/​2491956.​2491978
  16. Viswanathan, M., Viswanathan, R.: A higher order modal fixed point logic. In: CONCUR. LNCS, vol. 3170, pp. 512–528. Springer (2004)
  17. Watanabe, K., Tsukada, T., Oshikawa, H., Kobayashi, N.: Reduction from branching-time property verification of higher-order programs to HFL validity checking. In: Hermenegildo, M.V., Igarashi, A. (eds.) Proceedings of the 2019 ACM SIGPLAN Workshop on Partial Evaluation and Program Manipulation, PEPM@POPL 2019, Cascais, Portugal, January 14-15, 2019. pp. 22–34. ACM (2019), https://​doi.​org/​10.​1145/​3294032.​3294077
  18. Xia, L., Zakowski, Y., He, P., Hur, C., Malecha, G., Pierce, B.C., Zdancewic, S.: Interaction trees: representing recursive and impure programs in Coq. Proc. ACM Program. Lang. 4(POPL), 51:1–51:32 (2020), https://​doi.​org/​10.​1145/​3371119
  19. Yamada, R., Kobayashi, N., Sakayori, K., Sato, R.: On the relationship between dijkstra monads and higher-order fixpoint logic. A longer version, available from the second author’s webpage (2025)
Metadaten
Titel
On the Relationship between Dijkstra Monads and Higher-Order Fixpoint Logic
verfasst von
Risa Yamada
Naoki Kobayashi
Ken Sakayori
Ryosuke Sato
Copyright-Jahr
2025
DOI
https://doi.org/10.1007/978-3-031-91121-7_16