An extended account of contract monitoring strategies as patterns of communication

Abstract Contract systems have come to play a vital role in many aspects of software engineering. This has resulted in a wide variety of approaches to enforcing contracts—ranging from the straightforward pre-condition and post-condition checking of Eiffel to lazy, optional, and parallel enforcement strategies. Each of these approaches has its merits, but each has required ground-up development of an entire contract monitoring system. We present a unified approach to understanding this variety, while also opening the door to as-yet-undiscovered strategies. By observing that contracts are fundamentally about communication between a program and a monitor, we reframe contract checking as communication between concurrent processes. This brings out the underlying relations between widely studied enforcement strategies, including strict and lazy enforcement as well as concurrent approaches, including new contracts and strategies. We show how each of these can be embedded into a core calculus, and demonstrate a proof (via simulation) of correctness for one such encoding. Finally, we show that our approach suggests new monitoring approaches and contracts not previously expressible.


Introduction
Behavioral software contracts, originally introduced by Meyer (1992), have become an integral part of modern programming practice, where they are used to specify and ensure program correctness. In the first-order, functional setting, programmers write predicates (i.e., functions that take an input and return a Boolean value as a result) to check program properties at function boundaries: the pre-condition predicate is applied to the function input; the function is run on its input (if the pre-condition predicate holds); and the post-condition predicate is applied to the function result. If either predicate returns false, the program terminates with an error.
Programming, however, is more complex: programs utilize higher order functions, effectul operations, massive data structures, lazy evaluation mechanisms, and more. This added complexity must be addressed by the contract system: for higher order functions, we must delay contract enforcement (Findler & Felleisen, 2002); effectful 2 C. Swords et al. operations may affect values that were previously checked; massive data structures may be prohibitively expensive to inspect; and over-evaluating input may change program behavior.
While many of these systems initially appear incompatible, each of these approaches to contract enforcement share a common core: checking that a given program fragment satisfies a contract requires executing some verification code, and this execution is fundamentally distinct from the original program fragment. These two pieces of code proceed independently, synchronizing at specific points. Prior contract designs blur this distinction, fixing the evaluator interaction pattern in the host language, providing a single enforcement strategy to the programmer. If we preserve this distinction, however, these variations on evaluator interaction may be directly encoded, allowing programmers to vary monitor behavior on a per-contract basis, choosing the appropriate behavior for each contract.
Contributes and outline. This paper extends and refines the work presented by Swords et al. (2015) in the following ways: we simplify the underlying calculus presented by Swords et al. (2015), eschewing process tags (which governed termination behavior); present a modified encoding of contracts as patterns of communication that ensures each monitored term is evaluated in the correct process (whereas Swords et al. (2015) always evaluate monitored terms in the monitoring process) include a number of additional examples; introduce an additional monitoring strategy, fconc, for finally concurrent monitoring; and present an extended proof that our eager verification strategy simulates the λ CON calculus presented by Findler & Felleisen (2002).
The paper proceeds as follows: we describe and discuss previous contract verification variants (Section 2); describe how these variations may be verified by viewing contract monitors as separate evaluators (Section 3); present a concurrent process calculus as a unified system encoding multiple contract verification strategies (Section 5); encode a number of modern verification strategies in this framework (Section 6); demonstrate the multi-strategy approach to contract monitoring (Section 7); present a simulation of Findler & Felleisen (2002) as eager verification (Section 8); and present related works and conclude.

Background
\ In the conventional study of software contracts, a language designer extends a core calculus with monitoring facilities that adhere to a specific monitoring strategy, describing how contract verification interacts with the user program in precise semantics. This semantic specification is a permanent fixture of the language, dictating the behavior of contract verification across the entire program.
While modern contract software verification literature introduces a slew of such specifications (Ergün et al., 1998;Findler & Felleisen, 2002;Chitil et al., 2003;Hinze et al., 2006;Degen et al., 2009;Disney et al., 2011;Chitil, 2012;Dimoulas et al., 2013;Keil & Thiemann, 2015;Moore et al., 2016;Hickey, 2018), each variation makes distinct decisions about the various trade-offs for verification, including • Should we treat contracts as specifications we must verify, ensuring values adhere to these contracts regardless of their usage in the program? • Should the user program wait while verification occurs? • Should the user program have a role in verification (i.e., retrieving contract results)?
These questions do not have definitive "yes-or-no" answers, but each represent a gradient of answers has led to a number of semantic specifications for verification monitors that each take different stances on these questions. For example, the option contract system presented by Dimoulas et al. (2013) suggests that we should sometimes treat contracts as specifications we must verify, waiting while we do, and the user program plays a role in verification by choosing to postpone or eschew contracts. At the other end of the spectrum, we can eschew complete verification and removing the user's role in verification, but still suspend the program to check contracts, while we get spot-checking systems similar to those described by Ergün et al. (1998).
To further explore these variations, we introduce a language (similar to the core calculus presented by Degen et al. (2009) and CP CF presented by ): Most of these operations act as expected: we have variables x, values V (including contracts), lambda abstractions and application, conditional branching, binary operations, pairs with accessors, and errors (which raise "empty" errors for now, to simplify presentation-they would normally carry blame information). E also includes: • monitoring mon E 1 E 2 , which installs a runtime monitor that verifies the contract E 1 on the expression E 2 such that the monitor either returns the evaluated term (with potentially embedded monitors 1 ) or raises an error; • the predicate contract combinator pred/c E, where E must be a predicate function (i.e., returns a Boolean based on a single argument); • and the pair contract combinator pair/c E 1 E 2 , where E 1 and E 2 are subcontracts to check on the first and second elements of the monitored pair (respectively).
We may use these combinators to construct new contracts, such as a contract that verifies its input is a natural number, and, further, use this definition and the pair 4 C. Swords et al. contract combinator to define a contract that will verify its input is a pair of natural numbers: nat/c := pred/c (λn. n > 0) (1)
To determine the answer, we must fix a semantic contract verification strategy, choosing how to answer each of the questions above (and, as we shall see in Section 2.4, any further questions these answers may raise). Next, we examine the behavior of five different contract verification strategies, which each take different positions on these questions. We have selected these five because they are either well-represented in the literature (in the case of eager, semieager, and promise-based checking), have elegant encodings into our model (which directly correspond to the ownership flow model identified by ), or explore a various possible answers to our questions about the nature of verification. These are not, however, the only possible strategies; we discuss a number of additional strategies in Section 9.

Eager verification
Eager contract enforcement for software contracts, first presented by Meyer (1992), brought into the functional world by Findler & Felleisen (2002), and repeatedly refined (Ou et al., 2004;Findler & Blume, 2006;Flanagan, 2006;Degen et al., 2009;Degen et al., 2010;Greenberg et al., 2010;, presents the idea that contracts are fully-verified specifications, over-evaluating their input to ensure the contract holds while the user program waits for the verification result. To demonstrate this behavior, consider eagerly monitoring nat/c: (λx. 10) (mon nat/c (2 + 3)) → (λx. 10) (mon nat/c 5) → (λx. 10) (if (λn. n > 0) 5 then 5 else error) → * (λx. 10) 5 → 10 When the evaluator encounters the monitoring form (line 3), the user program (attempting to apply (λx. 10) to (2 + 3)) is suspended, while the monitor evaluates its input and verifies the contract. After the monitoring reduction ensures the contract holds, it yields control back to the user portion of the program with the monitored value (in this case, 5). If 5 had violated the contract, the monitor would subvert the user program to raise an error.
This contract enforcement strategy treats contracts as concrete specifications to fully verify, regardless of the program's execution trace (e.g., how it uses the contracted values). For example, consider taking the first element of a nat-pair/ c-contracted pair: fst (mon nat-pair/c (5, −1)) → * fst (mon nat/c 5, mon nat/c − 1) → * fst (5, mon nat/c − 1) → * fst (5, error) → * error Enforcing a pair contract with an eager verification strategy immediately and completely enforces each subcontract. In this case, even though the second element of the pair (−1) is unused in the program's result, the contract still detects that it is not a natural number and signals an error.
As we can see, eager contract verification has a number of drawbacks: • Treating contracts as fully verified specifications may inhibit verifying properties on infinite structures. For example, ensuring that each element of an infinite stream is a natural number will cause the monitor will diverge. • Suspending the user program, while the monitor proceeds can be computationally inhibitive. For example, consider mon prime/c (237 63 + 567) The user program waits while the contract monitor performs this check, which may bottleneck applications. Similar situations may occur when, e.g., ensuring each element of a hash map adheres to a specific property. If eager verification is the only monitoring strategy available, programmers may find it too expensive to check many rich properties of their programs. • Interrupting the user evaluator and over-evaluating inputs may produce different program behavior, and may not always preserve the underling program's meaning (Owens, 2012). For example, consider the following predicate (meant to be monitored on a function): If the function diverges on input 5, but otherwise behaves correctly over the course of the program, then monitoring this contract will cause divergence in a program that may have otherwise terminated.
These drawbacks stem from the fundamental assumption of eager contract verification: that contracts are concrete specifications that must be totally enforced as the user program encounters them.

Semi-eager verification
The over-evaluation in eager verification suggests an immediate alternative: we may maintain the user program suspension during verification, but postpone each individual contract's verification until the program demands the contracted value. This semi-eager verification strategy, originally presented by Hinze et al. (2006) and later refined (Findler et al., 2008;Degen et al., 2009;Degen et al., 2010;Chitil, 2012), specifies that only those values the program uses will have their contracts verified.
To this end, the monitor reduction "boxes up" the contract and the monitored expression as a value, suspending contract enforcement. When the user evaluator demands the value (e.g., the boxed expression occurs in evaluation position), the evaluator suspends the user program and enforces the contract, after which the program resumes with the monitored value (if the contract holds) or raises an error (if it does not).
To illustrate this behavior, consider evaluating the previous pair example with semi-eager monitoring (where we represent contract-expression boxes as contract | expression and reduction handles these as special forms when they occur in evaluation positions): fst (mon nat-pair/c (5, −1)) → * fst nat-pair/c | (5, −1) → * fst (mon nat/c 5, mon nat/c − 1) This example illustrates the primary behavioral difference from eager verification: in semi-eager verification, contracts are no longer strict specifications, but any value the program uses is correctly monitored. Such enforcement may prove invaluable: we may check only those values we use out of an infinite stream or hash-map, preserving program behavior, performance, and localized contract verification. Even so, semi-eager verification has its own drawbacks: • First, semi-eager enforcement is not faithful to the contract specification ): we will not detect errors in unused values, and thus cannot always trust our contracts as full specifications. This may also lead to programmer evaluating values, such as taking the unused second element of the pair, expressly for contract enforcement. • Second, semi-eager enforcement is not idempotent. Degen et al. (2009) observe verifying a contract in semi-eager verification may inadvertantly cause other, pending verifications to be performed on that value. In this situation, an unused value that violates its contract may raise an error if that contract is applied a second time. • Finally, this verification technique still suspends the user evaluator while verification proceeds, and thus some contracts may still prove too expensive.
As we can see, semi-eager verification is also not a catch-all solution.
A remark on function contracts. Function contracts present a unique problem when compared to other structural contracts: unlike pairs or larger structures, it is, in general, impossible to ensure that a procedure behaves correctly for every input (as they typically work over an infinite input space). To avoid this problem, Findler & Felleisen (2002) propose an alternative approach to function contracts wherein a function contract yields a new function, wrapping the input function in pre-and post-condition checks: mon (fun/c nat/c nat/c) (λn. n + 5) → λx. mon nat/c ((λn. n + 5) (mon nat/c x)) An extended account of contract monitoring strategies 7 This definition suggests that function contracts are natively semi-eager, utilizing the implicit delaying nature of λ to check values as they flow in and out of the function (since checking the function's entire domain and range is, in general, impossible). Other, more "eager" approaches would entail either probabilistic or static analysis for verification (Ergün et al., 1998;Xu et al., 2009;Nguyen et al., 2014). We postpone discussing these alternatives until Section 9 in order to keep function contracts similar, in nature, to their structural counterparts: each takes some structure as input and returns the same structure with contracts nested in it.

Promise-based verification
In order to address the concern of evaluator interruption, Dimoulas et al. (2009) introduce the notion of Future Contracts, presenting a concurrency model with a user process and a monitoring process wherein the user process communicates contracts and expressions to the monitoring process and the monitoring process concurrently performs contract verification, reporting errors to the user process at pre-determined synchronization points (Dimoulas et al. (2009) choose effectful operations to perform this synchronization). We may utilize a similar approach with inter-process communication and computational promises (Friedman & Wise, 1976), written [ e ] where e is the expression to retrieve the result when the promise is demanded by the user process. During monitor insertion, the user evaluator communicates the contract and expression to a concurrent process and resumes its computation, while the concurrent monitoring process proceeds with verification and communicates the result, fulfilling the computational promise.
For example, consider a program that expects a sorted list, generates a new encryption key, and then uses that key to encrypt the list. If generating a new encryption key is computationally intensive, it may be worthwhile to ensure the list is sorted concurrently 2 : 8 C. Swords et al. Similar to semi-eager verification, this promise-based verification model forces promises when they occur in evaluation position and, as a result, only contract results used in the final program output are communicated to the user evaluator.
Unlike eager and semi-eager verification, the user evaluator is not interrupted during monitoring, and may proceed with its own computation separate from contract verification. As Dimoulas et al. (2009) observe, this approach reveals a potential optimization in multi-processor settings: the user evaluator may proceed in parallel with the monitoring evaluator, allowing the user evaluator to spend less time awaiting monitoring results.
The decision to eschew contracts as concrete specifications and remove monitoring suspensions, unfortunately, has its own potential issues: • First, promise-based verification falls victim to many of the previous concerns of semi-eager verification (including lack of idempotence and verification antipatterns). • Second, the user evaluator may end up "wasting cycles" on speculative computation, performing operations that are unused after a contract signals an error (or worse, must be rolled back, such as in the case of side effects such as writing data to a file). • Third, the cost of communication and promises may dominate program performance. • Finally, effectful contracts (e.g., a contract that maintains internal state) no longer have a guaranteed execution order, and may yield unpredictable results.
As with eager and semi-eager verification before it, we can see that promise-based verification is also not a perfect-fit solution.

Concurrent verification
In an attempt to relax and address some of the semantic complexity of the previous verification approaches, we now consider an alternative, concurrent verification technique that forgoes reporting the result to the user evaluator. Instead, the monitoring process enforces the contract concurrently, either completing silently or detecting and reporting an error (and halting the entire computation). This concurrent enforcement may proceed as follows: User Process Monitoring Process This program will either result in 5 or raise an error; which should we expect? By removing the secondary synchronization point, we expose a further verification question: If the user evaluator does not explicitly ask for the contract result, should the language runtime await it anyway?
Either answer is valid, and each has further implications. If we do not wait, we get a concurrent, "best-effort" checking system: in the above example, we may either receive 5 or raise as the final answer, dependent upon process scheduling. While ultimately weaker than the previous verification techniques, this best-effort approach may be ideal for enforcing expensive properties (such as probabilistic primality checking) during a program. This concurrent verification strategy, however, has its own pitfalls: • First, concurrent verification may prove too weak to be reliable: a best-effort approach to verification may inhibit programmers from reliably ensuring program properties. For example, a probabilistic primality check contract may not find a counter-example, even if the number is not prime. • Second, scheduler-dependent results may only detect contract violations on some program executions. • Finally, effectful contracts may have unpredictable behavior.
To address the first two issues, we may instrument the language runtime to wait for these monitoring processes to run to completion before reporting the result of the user evaluator. This alternative, "finally-concurrent" verification approach recovers the guarantees lost in concurrent verification by ensuring that every contract we monitor is fully enforced before the user program terminates. In this above example, this means the error will be always reported before the user program can yield 5 as an answer. Even so, this finally concurrent approach has its own issues: • First, finally concurrent verification yields a similar flavor of over-evaluation as eager monitoring: enforcing a contract on a stream will never terminate, and as a result, it is possible to create contract monitoring processes that never terminate. • Second, effectful contracts suffer the same problems that concurrent and promise-based verification exhibit.

Unifying variations on verification
Each of the contract verification strategies we have seen have their own strengths and weaknesses based on how, precisely, each chooses to interpret and answer our verification questions. In light of these pros and cons, Degen et al. (2009) declare that "faithfulness is better than laziness" for lazy languages, advocating that concrete contract assurances are more valuable than any other properties. Conversely, Findler et al. (2008) identify a number of contracts where semi-eager enforcement is critical to maintain standard program asymptotics.

10
C. Swords et al. Swords et al. (2015) suggest that, for the practical programmer, none of these strategies is going to fit every use case in a program, and that programmers should be able to choose their verification strategy on a contract-by-contract basis to address different needs over the course of a program. For example, a programmer may wish to check that a tree is a binary-search tree in each of the following ways in the same program: • via eager monitoring after initial construction, to ensure that a list-to-tree procedure produces the correct structure; • via semi-eager monitoring, to check that each element touched during a binary tree lookup satisfies the contract while preserving asymptotics; • via promise-based monitoring, to inspect the tree while the program performs additional, unrelated operations; • and via concurrent monitoring, to ensure that a tree read from a file is sorted.
To facilitate this flexibility, we must give programmers a mechanism to select which strategy they would like at a per-contract basis, and move between them freely. To this end, we introduce a new check form: As with the mon operator, the check operation takes a contract and an expression to monitor. In addition to these arguments, check also takes a strategy argument-a value describing which enforcement strategy the monitor should use: We may use these strategies in combination with the previously defined contracts to produce each monitoring behavior. This revised check form also takes a blame argument b, which we elided for mon. This blame argument is the standard, indystyle three-tuple , and thus we elide its precise definition and further explanation. Blame inversion and dependent-style blame follow directly.
To demonstrate its immediate utility, consider a bst/c contract over binary trees, parameterized by a strategy indicating how to enforce each recursive check. This parameterization will allow us to write each of the contracts described above: • check eager (bst/c eager) tree B will eagerly ensure that a tree is a binary-search tree; • check semi (bst/c semi) tree B will return a tree that will check that each subtree is correctly-ordered as it is explored; • check prom (bst/c prom) tree B will create a cascading chain of monitoring processes for each node, and exploring the tree will synchronize with the appropriate processes at each level; • check conc (bst/c conc) tree B will concurrently enforce that the tree is a binarysearch tree using a similar set of cascading processes, but as a "best-effort" check; • and check fconc (bst/c fconc) tree B will do the same as the conc contract, but force the program to wait for it to complete before terminating.
These are not the only possibilities, however: the strategy argument to check describes how the top-level monitor should behave, while the strategy argument to bst/c describes how each recursive contract (i.e., the contract applied to each child node) behaves. This fine-grained control allows us to freely intermix strategies to produce additional verification patterns. For example, we may create a single promise-contained check that eagerly enforces each subcontract as check prom (bst/c eager) tree B.

Unifying verification
Our strategy descriptions thus far appear to be specialized semantic forms, each designed from the ground up to provide unique behavior. On the surface, this suggests that each strategy requires unique implementation facilities. This perception, however, is incorrect. While it may be possible to encode each strategy as a custom, unique entity as part of a specialized case for check, each of these strategies is, fundamentally, a small variation on the same theme: a monitoring evaluator subverts the user program, suspending it (or working concurrently), while it enforces the contract on the monitored expression. Each strategy variation directly corresponds to how and when this monitoring evaluator interacts with the user program, and how the user program proceeds in the context of these interactions. To illustrate this idea, consider evaluating the expression 5 + check eager nat/c (1 + 2) First, the user program will evaluate (1 + 2), yielding 3. Next, the monitoring expression check eager nat/c 3 will suspend the user program, while the monitoring evaluator ensures that 3 is a natural number. Finally, the monitoring expression yields 3 and the user evaluator resumes, evaluating 5 + 3 to 8 as the final result. This derivation is presented in the top-half of Figure 1, where we have indicated the user portions of the evaluation in blue and the monitoring portions in red . This value flow between evaluators is reminiscent the ownership model described by , where we account for the contract itself as an additional party.
Further, observe that we may separate out the monitoring portion of the program from the user portion and explicitly model these evaluators and interactions (Disney et al., 2011;Swords et al., 2015). We can apply this separation to our previous example, yielding the derivation in the bottom-half of Figure 1, where the user and monitoring programs explicitly interact to compute the final result.
This revised derivation reveals the fundamental nature of contract monitoring: a contract monitor is a separate evaluator communicating with the user program. And now we may vary the pattern of communication to produce our varied contract enforcement strategies. This explicit account of interactions allows us to explore the enforcement design space, expressing contract verification strategies in a single, unified framework using evaluator interactions in order to examine their unique behavior and interactions in a uniform system.

+ mon nat/c (1 + 2)
5 + mon nat/c 3 5 + if nat? 3 then 3 else raise 5 + if nat? 3 then 3 else raise Fig. 1. Separating an eager, flat contract into a pattern of communication. (We take nat? to mean λx. x > 0 to simplify our presentation.) The first image depicts a single evaluator indicating the different evaluation components of a software contract system, where the user components are colored blue and the software contract system component is colored red . The second image depicts performing verification in an explicitly separate evaluator.

A calculus for contract monitors
To solidify this notion of monitors-as-communication, we first need a calculus that will let us reason about multiple evaluators and their interactions. We start with a calculus based on Concurrent ML (Reppy, 1993;Jeffrey, 1998;Reppy, 1999), given in Figures 2 and 3, including a term language e with values v and term reduction " −→", and a concurrent reduction relation "⇒," which extends term reduction to a finite set of interacting processes. This calculus is, in general, unremarkable: as with most modern programming language, our calculus supports process creation and communication events, raising and catching errors, and delaying and forcing individual terms.

Term language.
Expressions e include variables, values (including λ-abstractions and communication channels ι), application, and a number of canonical operations (Pierce, 2002). A full type system, complete with a proof of type soundness, is provided as an electronic appendix. 3 Further, we elide the definition of δ, which is a partial function that encodes binary and unary operator behavior. We also include the following: • delay, a delaying construct that produces thunk-like objects (Ingerman, 1961); • force, which evaluates delayed expressions (and any other term to a value); • raise and catch, specialized to blame values: if raise v occurs without an error handler (i.e., in a D-Context), we discard the surrounding context; • and opaque blame values B, which represent blame information such as positive, contract, and negative positions (following ).
We postpone defining check and our contract combinators until Section 6.

Processes & communication.
The concurrent evaluation relation "⇒" extends the " −→" relation with a finite set of processes (i.e., terms with associated process identification numbers) with additional rules to handle the process-level operations for channel creation (chan), process creation (spawn), and process communication (read, and write). We define process identification numbers π such that e π constitutes a single process. A process configuration K, T , P has three components: • K is a set of channel names for the configuration; • T is a set of process identification numbers π that indicate the termination set of a configuration, allowing us to define answer configurations: Definition 5.1 (Answer configuration) A configuration K, T , P is considered an answer configuration if, for every π ∈ T , there is a process e π ∈ P and e −→.
• and P denotes the set of processes in the configuration.
When convenient, we elide K and T from our traces and write P + e π to mean P ∪ { e π } to simplify presentation. The process reduction rules proceed as: • [ProcStep] describes internal process reduction, lifting the term evaluation relation " −→" to configurations. • [Spawn] and [FSpawn] describe process creation, which selects a new process identification number π and create a new process with the provided expression. The originating process proceed with unit. In the case of [FSpawn], the new process identification number is also added to T , ensuring the process will complete before the configuration is an answer configuration. • [Channel] describes channel creation, where we select a new channel name ι, add it to K, and continue with it in the process. in Figure 3. If two processes are matched, they may communicate as a reduction.
In λ ⇒ /c , processes are immortal-if a process evaluates to a value v, it will remain in the process set without further reductions.

Contracts as patterns of communication
With our core calculus in place, we may now define contract monitoring strategies in a single, unified system, expressing each in terms of the language primitives we have introduced in λ ⇒ /c . To begin, we define three contract combinators, pred/c, pair/c, and fun/c, to use for our discussion: pair/c Δ = λcon 1 strat 1 con 2 strat 2 . λpair blame. (check con 1 strat 1 (fst pair) blame, check con 2 strat 2 (snd pair) blame) In this encoding, a contract is a procedure that takes a value and a set of blame information, yielding a term of the same type or raising an error. As before, the pred/c contract combinator take a predicate and produces a contract that checks that predicate. Similarly, the pair/c contract combinator takes two contracts and associated strategies to enforce on each element of a pair (under the respective strategies), yielding a pair contract. Finally, the fun/c contract combinator takes two contracts and associated strategies and produces a function contract. When we check this contract on some function f, the combinator yields a new function (to "stand in" for the previous function), checking the first contract, or pre-condition, on each input to this contracted f and checking the second contract, or post-condition, on each of f's results (Findler & Felleisen, 2002;Strickland et al., 2012). With these combinators in place, we turn our attention to the contract monitoring strategies described in Section 2, providing semantic definitions for each in terms of λ ⇒ /c .

Eager contract monitoring-interrupting the user evaluator
We begin with eager monitoring, where each contract is completely verified at assertion time. To model this strategy as a pattern of communication, the initiating process: (E U1 ) creates a new communication channel ι; (E U2 ) spawns a monitoring process that will evaluate the contract and communicate the result across ι; (E U3 ) provide the (evaluated) subject value to the monitoring process across ι; (E U4 ) and retrieve the result from the monitoring process across ι and handle the results (as explained below).
Dually, the monitoring process: (E M1 ) receives the subject value v across ι; (E M2 ) runs contract c on the value with the provided blame information; (E M3 ) next, if the contract returns a value, the monitoring process injects it right and, similarly, if the contract raises an error, the monitoring process catches that error and injects it to the left; (E M4 ) and writes the injected value across ι to the user process.
This interaction is presented in Figure 4 (with the monitoring process colored red ). These two evaluators synchronize at (E U3 , E M1 ), to communicate the subject value to the monitoring process, and again at (E U4 , E M4 ), to communicate the verification result. Because read is blocking, the user process will wait at (E U4 ) until the monitor completes. We begin our definition of check by encoding this interaction as eager verification 4 e := · · · | check e e e e Recall that the check language form accepts the contract to check, the strategy to check it with, the expression to check it on, and blame information. Note, however, that we do not evaluate the monitored term until after the constructing the correct monitoring pattern, which allows individual strategies to control (or delay) when to evaluate the contracted term (such as in the case of semi monitoring). The conres helper interprets the monitor result in the user process (as indicated in E M4 ): If the value is left-injected (indicating a contract violation), we re-raise the error in the process expecting the contract result, and, similarly, if the value is right-injected (indicating that check did not raise an error), we return it to the process. 5 To demonstrate this monitoring structure, consider the program in Figure 4, which checks that (2 + 3) is a natural number. The monitor creates a process, communicates the evaluated version of the term, and retrieves the results. If the monitored expression had been −1 instead, the user process would instead terminate with an error.
each level, the initiating evaluator writes a value across a channel and awaits the monitoring evaluator's result. Function contracts proceed similarly, but without the nesting monitors, illustrating one of the main revisions over the work presented by Swords et al. (2015): while their work directly embeds each monitored term in the monitoring process, this reformulation explicitly evaluates the monitored term in the user process before it is given to the monitor. For example, consider monitoring the following function contract on λx. 1: nat-fun/c eager Δ = fun/c nat/c eager nat/c eager We present a trace of its usage in Figure 6. As evaluation proceeds, the term ((λx. 1) (check nat/c eager 5 (invert B))) occurs in the user process, triggering the pre-condition check while the post-condition check awaits the function result. After the pre-condition is complete, the user evaluator performs the actual function application ((λx. 1) 5), reflecting the value flow in Figure 1. Even so, these last two examples illustrate the over-eager nature of eager verification: while we did not inspect the second element of the pair (5, −1) and the function λx. 1 did not use its argument, we still checked that each was a natural number. We can see, now, that the fundamental problem is preemption: eager evaluation explicitly suspends the initiating evaluator while verifying the contract, only resuming the initiating evaluator once it is complete. As a result, the overall computation performance is ultimately tied to contract performance, and to alleviate this situation, we merely need to vary the pattern of communication between these evaluators.

Semi-eager contract monitoring-postponing contract verification
Following the order in Section 2, we next encode semi-eager monitoring, indicated with the semi strategy. Recall that, in semi-eager verification, the monitor must suspend enforcement until the user evaluator demands the result, which we previously described as "boxing up" the contract and value. To model this monitoring strategy as patterns of communication, the initiating process: (S U1 ) creates a delayed expression d and returns it to the user.
When the delayed expression is forced, the forcing process ( not necessarily the initiating process): (S U2 ) creates a new communication channel ι; (S U3 ) spawns a monitoring process to evaluate the contract and communicate the result across ι; (S U4 ) provide the (evaluated) subject value to the monitoring process across ι; (S U5 ) and retrieve the result across ι and handle the results (via conres).
Dually, the monitoring process: (S M1 ) receives the subject value v across ι; (S M2 ) runs contract c on the value with the provided blame information; (S M3 ) injects the result appropriately; (S M4 ) and writes the injected value across ι to the user process.
is presented in Figure 7 (with ). We extend our definition of check with this encoding: This implementation directly corresponds to the eager implementation in Equation (7), except the entire verification expression is delayed (highlighted in yellow to show its addition), packaging up the verification computation until an evaluator forces it. For example, we may semi-eagerly verify that (2 + 3) is a 22 C. Swords et al.
natural number. We sketch this verification Figure 7: when we force the delayed cell, the evaluator creates a new process, performs the contract verification, and continues the program with the result, yielding 125. For structural contracts, this system of delaying and forcing contracts gives programmers immense control over which parts of the structure are monitored. For example, a semi-eager pair contract that ensures each element is a natural number may forgo checking unused parts of the pair: 6 { force (fst (force (check nat-pair/c semi semi (5, −1) ))) π 0 } ⇒ * { force (fst (delay ..."check nat/c on 5"... , delay ..."check nat/c on −1"...)) π 0 , ...} ⇒ * { force (delay ...check nat/c on 5...) π 0 , unit π 1 } ⇒ * { 5 π 0 , unit π 1 , unit π 2 } Since the user evaluator never forces the second nat/c contract (that would have checked if −1 is a natural number), the program completes without enforcing the contract. In general, semi-eager verification allows users to verify precisely those values they require.
On the use of delay and force. In semi (and prom, below) monitoring, we use delay to produce delayed expressions as values that the user must explicitly force. We take this as a necessary evil to facilitate our discussion: some verification strategies require fine-grained delaying and forcing behavior in call-by-value calculi to correctly recreate less-eager evaluation mechanisms. We elect to do this with explicit delay and force operations in our presentation in order to explicitly clarify the nature of the evaluator interactions for these strategies, allowing force to propagate across our user programs. We tolerate this intrusion in our presentation to better explain the nature of these synchronizing monitors in order that the working semanticist may directly compare how and when the user evaluator interacts with them. In a programming language intended for everyday use, however, this forcing mechanism may clutter the program and inconvenience the programmer. As such, we suggest that they should be concealed in a language implementation, using implicit forcing at evaluation sites (and some transparent structure, such as, e.g., chaperones (Strickland et al., 2012) for delaying structure) to remove this complexity.

Promise-based contract monitoring-concurrent checking with synchronization
Our next contract verification strategy is promise-based verification, indicated by prom, which returns a promise to the initiating evaluator while verification proceeds concurrently. Unlike the box-driven description in Section 2, we utilize delay (and read's blocking nature) to provide promise-like behavior in verification results: when the initiating process forces the promise, it will block until the verification is complete (if the verification has previously finished, the initiating process will receive this result immediately). To model this strategy via patterns of communication, the initiating process: (P U1 ) creates a new communication channel ι; (P U2 ) spawns a monitoring process that will evaluate the contract; (P U3 ) provides the (evaluated) subject value to the monitoring process across ι; (P U4 ) and finally, returns delay (conres (read ι)) as our "promise." Dually, the monitoring process: (P M1 ) receives the subject value v across ι; (P M2 ) runs contract c on the value with the provided blame information; (P M3 ) injects the result appropriately; (P M4 ) and writes the injected value across ι to the user process.
These two evaluators synchronize twice: first at (P U3 , P M1 ), and later at (P M4 ) when the user process forces the delayed expression created in (P U4 ). The forced expression will perform a blocking read across ι, receiving the contract result via conres. This interaction is presented in Figure 8, and we extend check to support prom as: check con prom exp B → let i = chan in seq (spawn (write i (catch inl (inr (con (read i) B))))) (write i exp) ( delay (conres (read i)))) As with semi, this implementation directly corresponds to the eager implementation in Equation (7), aside from the addition of delay to delay reading the result. This should be unsurprising: the variation between eager and promise-based contract monitoring is precisely when the initiating evaluator receives the answer, allowing programmers to perform secondary computations while monitoring continues concurrently. An example of this enforcement interaction is given in Figure 8. Moreover, if these concurrent processes are run in parallel, the prom strategy allows contract verification to happen while speculatively performing additional computation.
In both semi and prom verification, the initiating process is given precise control over how to retrieve the contract result, using the same mechanism in both places. Note, however, that this equivalence assumes that the monitored term and contract are both pure; if either is not, semi and prom would no longer be interchangeable.

Concurrent contract monitoring-complete evaluator decoupling
Our next strategy, conc, eschews having a process retrieve the monitor result to provide concurrent contract verification. Instead, the monitoring evaluator proceeds concurrently without reporting its result. Modeled as patterns of communication, the initiating process: ( f 5) + (force (delay (conres (read ι )))) 120 + (force (delay (conres (read ι )))) 120 + (conres (read ι )) 120 + (conres (inr 5)) 120 + 5 125 write i (catch inl (inr (nat/c (read ι ) B))) write i (catch inl (inr (nat/c 5 B))) write i (catch inl (inr 5)) write ι (inr 5) These two evaluators synchronize once, at (A U3 , A M1 ), to communicate the subject value. This interaction is presented in Figure 9 (with the monitoring process colored Using our previous definition of answer configurations, we see that conc contracts represent contracts that may not finish; if the contract is still running when π 0 terminates, its result is ignored. For example, consider concurrently enforcing nat/c on −1: Our ⇒ relation is nondeterministic, and thus this monitor may or may not complete before the initiating evaluator, resulting in "best-effort" checking. For structural and functional contracts, this approach will yield numerous, nondeterministic processes, meaning errors may be reported non-deterministically if there are multiple contract violations. This has the downside of so-called "heisenbug"style errors, but the upside is that programmers may utilize concurrent behavior for weak, long-lived contracts. It is also imaginable that these violations may be reported as "warnings" to the programmer instead, indicating problematic values without bringing the program to a halt.
⇒ * { check nat/c conc ((λx. x + 1) 5) B π 0 , unit π 1 , nat/c 5 (invert B)) π 2 } ⇒ * { check nat/c conc 6 B π 0 , unit π 1 , 5 π 2 } ⇒ * { 6 π 0 , unit π 1 , 5 π 2 , nat/c 6 B) π 3 } This enforcement has a few oddities: 1. We call the top-level check with eager: if we had used conc, the function contract combinator would have produced the monitored procedure in the monitoring process, and the user program would have proceeded with λx. x + 1. It is conceivable that checking a function contract with conc may perform some sort of enumerative analysis, run concurrently, checking the function against inputs for the remainder of the program's run. This approach, however, has a number of drawbacks, including input-based dispatched to determine if the value is a procedure (via, e.g., Racket's procedure? operation (Flatt & PLT, 2010)), introducing non-uniform strategy behavior. Clojure's core.spec (Hickey, 2018) adopts a similar approach for higher-order function contracts, where higher order function inputs are randomly checked with sampled values to ensure they conform to the specification. We encode this strategy in the Section 9. 2. The user process π 0 proceeds without regard for the pre-or post-condition enforcement, while this may not be the case, based on scheduling, it further illustrates the "best-effort" nature of concurrent verification.
As previously discussed, this verification technique may often be "too weak" for many properties that a programmer must rely on.

Finally-concurrent contract monitoring-verification without synchronization
In order to provide programmers with "start and forget" verification with stronger guarantees, we introduce fconc verification. Similar to conc verification, fconc monitoring processes elides secondary synchronization with the initiating process. Unlike conc, however, we ensure the monitor completes before the configuration is considered "done." To this end, we us the spawn variant fspawn, which creates a new process and adds its process identification number to a list of final processes T , ensuring any answer configuration includes its full evaluation. With this system in place, the initiating fconc process: This interaction is presented in Figure 10 (with the monitoring process colored red ), which is nearly identical to Figure 9, with the addition of the answer configuration in green . The only difference between fconc and conc is this notion of process finality, and thus its implementation simply exchanges spawn for fspawn: When we use fconc to assert a contract, we may now trust that the contract will run to completion before the program enters an answer configuration: Even though one contract raised an error, we must still wait for each fconc contract to complete before termination. This "start and forget" contract verification technique exposes a new avenue for verification: programmers can, e.g., read in a file and speculatively start examining and using the input while being sure that, before the program is done, they will know the data is correct.

Multi-strategy contracts
Beyond choosing which strategy to use for each contract, programmers may also freely intermix strategies in λ ⇒ /c , yielding flexibility and utility beyond traditional contract systems. First, we summarize our strategies so far, giving their implementations together in Figure 11.

A flexible binary-search tree contract
Our first example is our strategy-parameterized binary tree contract from Section 3. While it is possible to construct this contract in λ ⇒ /c , we take some liberties here for simplicity of presentation, namely 28 C. Swords et al.
• We assume we may directly match on trees with named constructors: case (t; leaf B e 1 ; node val t left t right B e 2 ) (otherwise, we would have to define a binary tree as a sum type); • and we assume a fixpoint operator μ as

(μf x.e) v → e[μf x.e/f][v/x]
Recall from Section 3 that our binary-search tree contract should act as follows: • check eager (bst/c eager) tree B will eagerly ensure its input is a binary-search tree; • check semi (bst/c semi) tree B will return a tree that will check that each node is correctly-ordered as it is explored; • check prom (bst/c prom) tree B will create a cascading chain of monitoring processes for each node, and exploring the tree will synchronize with the appropriate processes at each level; • and check conc (bst/c conc) tree B will concurrently enforce that the tree is a binary-search tree using a similar set of cascading processes.
• and check fconc (bst/c fconc) tree B will do the same as the conc contract, but force the program to wait for it to complete before terminating.
To define bst/c, we must check that each value in the left sub-tree is less than (or equal to) the node's value and each value in the right sub-tree is greater (or equal to) than the node's value. To do this, we must propagate node values downward through subcontracts. This requires a dependent tree contract, wherein each subcontract is given the current node's value as an input before enforcement. We define this combinator, tree/dc, as: This combinator takes seven arguments 7 1. c leaf is a contract for leaf nodes ; 2. s leaf is the strategy describing how to enforce c leaf ; 3. c node is a contract for internal tree values; 4. s node is the strategy describing how to enforce c node ; 5. c left and c right are two procedures that expect a node value as input and yield the appropriate contracts for the left and right sub-trees (respectively); 6. and s rec is the strategy describing how to recursively enforce the resultant contract on the left and right subtrees.
We can use this dependent contract combinator to define bst/c as 8 : This contract ensures that each leaf of the tree is any value and each internal value in the tree is within the correct numeric bounds. As bst/c eager, this contract must traverse the entire tree to enforce this constraint, requiring O(n) time (whereas a insertion algorithm would require O(log n) time in a sorted tree). We can forgo such a strong guarantee, however, and use bst/c semi to enforce the invariant on exactly the nodes we visit during the program, recovering O(log n) complexity for insertion. Furthermore, we can completely decouple the evaluation via bst/c prom, starting the entire assertion in concurrent processes and only synchronizing with (and waiting on) those nodes required by the program, perform best-effort verification with bst/c conc, and even use fconc for finally concurrent, start-and-forget verification. Further, we can check bst/c eager under prom, constructing a promise that will concurrently enforce the entire contract. And each of these different variations sprout forth from the same definition of bst/c. Even further, we may define a secondary version of bst/c, called call bst/c s , which takes an additional strategy s 2 to use for node value contract enforcement, allowing us to control exactly when to verify each value in addition to the general recursive verification scheme: bst/c s := μ bst/c s s 1 s 2 lo hi.
tree/dc any/c eager (pred/c (λx. (lo 6 x) && (x 6 hi))) s 2 (λv. bst/c s s 1 s 2 lo v) (λv. bst/c s s 1 s 2 v hi) s 1 In this definition, we use s 2 to enforce the node predicate at each level.

A lazy tree fullness contract
Our second example tackles the problem of lazily ensuring that a binary tree is full (that is, each node's subtrees have the same height). Findler et al. (2008) identify such checks as requiring upward value propagation through monitored structures, which is generally impossible with structural contracts. To illustrate this problem, we define the predicate full? and use it to create a predicate contract: full? := let f = μ full tree . case tree (tree; leaf B0; node v tl tr Blet hl = full tl hr = full tr in if (hr = hl) && (hl > 0) && (hr > 0) full/c := pred/c full?
In general, we must traverse an entire tree to know if it is full: each node must first inspect its children, using the recursive results to determine if it is full, propagating heights upward to ensure the property. This style of value propagation through monitored structures generally inhibits semi-eager enforcement: if we enforce full/c using semi on a tree t, the monitor will traverse the entire tree when forced. Alternatively, we can adopt the side-channel style of contract definition presented by Swords et al. (2015), which allow multiple contracts to collaborate to ensure global properties. Using the conc strategy and communication, we may postpone checking any contract until its subcontracts are complete by having each contract communicate with its subcontracts. While complex in concept, the only additional facility we require is a choice-based reading operation (to allow us to communicate with multiple subcontracts at once).
This choice operator is a straightforward addition to λ ⇒ /c : we extend the "matches" relation from Figure 3 to support choice as: Each contract invocation is parameterized by a communication channel i, indicating where to write the current node's height. At leaf nodes, the contract writes 0 to i and succeeds. At internal nodes, we pass two fresh channels, i l and i r to the left and right subtrees respectively. Next, we assert (full i l ) and (full i r ) on the appropriate subtrees, utilizing the dependent contract to delay these invocations until usage time (to prevent divergence). Each of these subcontracts are monitored with semi and thus will not be verified until the initiating process demands these subtrees. Finally, the node value contract, monitored with conc, retrieves its subtree heights across i l and i r . If these two heights are equal, the contract writes the appropriate height across i and succeeds (triggering its parent's fullness test); if not, the contract signals a violation.
This communication pattern allows each contract to propagate values via sidechannel communication, working together to lazily establish global properties about programs. Such a contract is only possible after full separation of the monitor evaluator from the user evaluator and exposing communication tools to contract writers, facilitating contract verification in a custom-crafted traversal of the monitored structure. 32 C. Swords et al. Findler and Felleisen, 2002 if true then c 1 else c 2 c 1 if false then c 1 else c 2 c 2 n 1 + n 2 n 1 + n 2 n 1 ≤ n 2 true (if n 1 ≤ n 2 ) n 1 ≤ n 2 false (if n 1 n 2 ) . . . Fig. 12. A subset of the λ CON language from Findler & Felleisen (2002). We have renamed their E to C and e to c to avoid ambiguity.
Effectful tree fullness. Unsurprisingly, this is not the only solution to lazily ensuring tree fullness. Utilizing effectful contracts, we can imagine a dependent contract that keeps track of its recursion depth and, in each leaf node, reports this depth to a secondary process. This secondary process will then raise a contract violation if it ever received two disagreeing depths, indicating the tree is not full. This style of effectful contract verification allows programmers to check global properties with less overhead.

/c
We set out to provide a unifying framework for contract semantics, a sort of "assembly language" target for recreating, understanding, and comparing contract strategies. To demonstrate our accomplishment, we now prove that eager in λ ⇒ /c simulates the eager contract verification semantics presented by Findler & Felleisen (2002), up to alpha-equivalence and unit elimination. This proof is, in a sense, straightforward: our work follows Findler & Felleisen (2002) to recreate eager verification in λ ⇒ /c in terms of interacting evaluators, and thus our proof is primarily concerned with extracting and recovering the individual evaluators for contract monitoring, separating the user portions of the program from the monitoring ones.
We start with the core language λ CON presented by Findler & Felleisen (2002), given in Figure 12. This λ CON definition elides a handful of forms from the version presented by Findler & Felleisen (2002), including list and fixpoint operations (since neither are relevant to the discussion) and, more importantly, their outer val rec form, defined as follows (with appropriate evaluation contexts to match, which evaluate the bindings before the body): Here, each binding has two values associated as x : V 1 = V 2 , where the first represents a contract on the second. Findler & Felleisen (2002) install these contracts on each occurrence of x in the program via their I operator (given in Figure 14 of their technical report) such that, if x is bound as val rec x : e 1 = e2, then each usage site of x is rewritten as x e 1 ,x,n to enforce the contract e 1 (where x indicates the positive blame party, which is the binding itself, and n represents the negative blame party, which is defined by the variable's context). Findler & Felleisen (2002) provide this machinery to more closely match the module interaction system and blame coordination in Racket, which is unnecessary for demonstrating that their individual monitors proceed via eager verification. As such, our simulation assumes that each clause in the outer val rec form has been completely evaluated and substituted in, eschewing p and d syntax forms (and their associated evaluation contexts).
Our simulation works via three translation relations from λ CON to λ ⇒ /c , defined as " ", "→ → e ", and "→ → v " in Figures 13 and 14. This translation relies on one additional modification to λ CON : Findler & Felleisen (2002) use their language's if operation to perform predicate contract verification, and we must be able to identify when such an expression is a contract verification expression (as opposed to a conditional expression in the user program portion) so that we can extract it into a separate process. In order to distinguish between conditionals that are part of contract verification (e.g., if contract value then value else error) from other if expressions, we "recolor" the if expressions to indicate their origin: • We color each if expression that originates in the user program with •, indicating it is part of the user program. • We modify → in λ CON to produce if • forms as: Evaluation for both if • c then c else c and if • c then c else c otherwise proceed as if c then c else c in λ CON , and now our translation can determine which if expressions are part of contract enforcement in order to correctly translate them into λ ⇒ /c . The operator relates a term c with an expression e, a set of new channels K, and a set of processes P , where the translated expression is meant to fill process π 0 . The additional → → e and → → v helper relations translate terms and values in λ CON into equivalent term-level expressions in λ ⇒ /c , respectively. The intuition is that acts as the main translator, focusing in on the next redex of the program, while → → e evaluates each unevaluated portion of the program (i.e., c occurrences in context C) and → → v translates each evaluated portion of the term (i.e., V occurrences in context C). Next, we define a series of lemmas and finally prove our simulation result: • We appeal to alpha equivalence for our reduction in order to avoid the need to explicitly track and name channels and process identifiers in terms of their creation, so that we do not need to worry that the exact channel name or process identifier is used between steps, only that each name is used alpha-equivalently. • We disregard processes of the form unit π in our embedding; many of these are left over when contracts are done checking, and ensuring each exists would require keeping track of each contract checked up until the current point in the derivation.
Next, we note that the termination set will always be precisely {π 0 } since neither our translation relation nor eager monitors will introduce a fspawn term, and thus we do not need to consider the termination set for the proof. Recall, also, that we distinguish contract-checking if expressions from others for translation purposes. We proceed by induction on "c −→ c . Except for contract enforcement operations, our translation directly preserves the source language syntax, and thus we only present the contract-related cases in detail: Using , we have: Since C is static, the term inside of C is the next redex and thus it will be translated by → → v and → → e , whereas V contract(V 2 ),p,n 1 will be translated by (via Lemma 8.2). Furthermore, x then x else raise b fresh ι fresh π p = write ι (catch inl (inr (V 2 V 1 B))) π V contract(V 2 ),p,n 1 ({ι}, {p}, conres (read ι)) and c 1 (K, P , e 1 ) c 2 → → e e 2 c 3 → → e e 3 fresh ι fresh π p = write ι (catch inl (inr (if e 1 then e 2 else e 3 ))) π if • V 2 V 1 then V 2 else blame(p) Thus, P = P 0 {p} and K = K 0 {ι} (taking ι = ι and π = π ). Then, it is sufficient This follows directly because p reduces to p by our definition of term reduction relation " −→" and [ProcStep] in the "⇒" relation.
We use the same argument to deal with context C as in the previous case, yielding some K 0 and P 0 for the reduction. Then, where the channel and processes in the premises are empty by Lemma 8.1 and e fc is the result of the translation The right-hand side of the reduction proceeds as (K, P , e 1 e 2 ) The exact shape of e 2 depends upon the shape of V 3 . Since c is well-typed, it is either a flat or function contract. We proceed by consider each, appealing to this series of reductions to prove each: fresh π p = write ι (catch inl (inr (e 3 e 2 B))) π V contract(V 3 ),p,n 3 ({ι}, {p}, conres (read ι)) Thus, it is sufficient to show K 0 , P 0 + E[(check e 4 eager (e 1 (check e 3 eager x (invert B))) B) e 2 ] π 0 ⇒ * K 0 {ι, ι }, P 0 + E[seq (write ι (e 1 (conres (read ι )))) (conres (read ι))] π 0 + write ι (catch inl (inr (e 4 (read ι) B))) π + write ι (catch inl (inr (e 3 e 2B ))) π which follows from our reduction semantics in λ ⇒ /c .
Since both V 2 and V 3 are values, we translate this as which follows from our reduction semantics and unit equality to account for the missing unit-value process created by constructing the contracted function value for V 2 .
Finally, we state the embedding theorem as

Proof
First, no translation will produce an fspawn form, and thus T remains constant. Then the proof proceeds by induction on the length of −→ * and Lemma 8.3. This proof demonstrates that our approach to eager monitoring faithfully recreates the original presentation and, more generally, that defining contracts as patterns of communication maintains previous models while exposing their internal workings at a finer granularity. Additional simulation proofs will be more complex, but follow this same approach: syntactic munging plus a little process management.

Related works
The first language for specification and verification was Gypsy, introduced by Ambler et al. (1977). Later, Meyer (1992) introduced the term software contract with the language Eiffel, along with our moderns notions of pre-and post-condition contract enforcement, and Findler & Felleisen (2002) brought software contracts into functional languages.

Additional software contract verification techniques
After Findler & Felleisen (2002) brought software contracts into functional programming, a cottage industry of software contract verification techniques sprung up Findler et al., 2008;Degen et al., 2009;Dimoulas et al., 2009;Chitil, 2012;Dimoulas et al., 2013;Moore et al., 2016). Swords et al. (2015) provides a framework for expressing their interactions, and we extend and revise that work. In this work, we selected five strategies because of their frequency in the literature, their apparent utility, and their direct encodings. Unsurprisingly, these are not the only variations on contract verification. Option Verification. Dimoulas et al. (2013) introduce option contracts, which allow modules to optionally enforce contracts or eschew contracts while adopting responsibility, avoiding unnecessary contract enforcement. It is possible to replicate this behavior in λ ⇒ /c , using the same pattern of encoding and structures as Dimoulas et al. (2013) in the presence of the option strategy. Such an implementation would be particularly effective in a chaperone-based system. Random Checking. Ergün et al. (1998) and Dimoulas et al. (2013) each describe random testing for program correctness, which has since gained popularity in Clojure's core.spec library (Hickey, 2018), where their higher order function contracts adopt generative checking, producing sample inputs to ensure that the provided function behaves correctly, and their flat contracts are verified eagerly. We can replicate this behavior using a gen strategy that accepts a generator g and ensures its input adheres to such generated values: If f is a function, the check-fn procedure will use the provided generator to randomly test it before returning it; otherwise, the strategy behaves as eager.
Future Contracts.  introduce concurrent contracts via future contracts, which we base our prom contract verification on, wherein terms and their contracts are sent, as messages, to a secondary evaluator for verification. It is straightforward, however, to imagine a strategy that sends contract-expression pairs to a global, concurrent process and extending effectful operations with synchronization operations. Moore et al. (2016) introduce contracts to model authorization and access control (Moore et al., 2016), which provide a domainspecific language for writing security-centric contracts. It is conceivable to recreate such behavior as a strategy with customized contract inputs, wherein the strategy inspects the structure of its contract and acts accordingly. Doing so would require modifying λ ⇒ /c to support the dynamically bound parameter scope system this contract model requires, but we speculate that the process portion of our calculus may be utilized for this task. Lazy Contracts. First described by Chitil et al. (2003) as assertions (without blame mechanisms), Degen et al. (2009) later described lazy contracts as allowing the user evaluator to drive the contract evaluator, wherein the contract evaluator suspends verification until the user evaluator evaluates the subject term. For example, checking a predicate on a pair will not force the pair will suspend verification until the user evaluator evaluates the pair; if the user program never does, the monitor will never verify the contract. Degen et al. (2012) give a sketch of implementation, suggesting that the monitor spawn a separate thread that waits until the arguments to the assertion are evaluated and then executes the contract.

Security Enforcement Contracts.
This model of monitoring, when translated into λ ⇒ /c , demonstrates its intrusive interaction with the main evaluator: to facilitate this user-driven monitoring, we must construct a layer of indirection for both evaluators such that the user evaluator is forcing an expression drives the lazy monitor. Swords et al. (2015) give a sketch of a possible implementation that includes recursively parsing the input expression e. This approach closely mirrors the implementation provided by Degen et al. (2010), wherein individual call-by-need cells register callbacks for contract monitors. Even so, Degen et al. (2012) observe that such lazy verification violates basic blame consistency, and thus has questionable utility. Dimoulas et al. (2009) and Disney et al. (2011) each explore the notion of contracts using message passing, and, further, the runtime verification literature contains a number of additional examples of using messages to verify program properties (Havelund & Rosu, 2001;Barnett et al., 2005;Chen & Roşu, 2007). In each case, however, the work has different design goals from Swords et al. (2015) and our work. Dimoulas et al. (2009) model and explore concurrent contracts; Disney et al. (2011) precisely model and explain temporal contracts with non-interference and trace completeness; our work explores modeling contracts as secondary evaluators and varying evaluator interactions to encode multiple verification strategies in the same framework. Dimoulas et al. (2009) introduce concurrent contracts via future contracts, wherein terms and their contracts are sent, as messages, to a secondary evaluator for verification. Dimoulas et al. (2009) observe that this approach is familiar in the broader runtime verification community. We extend the idea of a secondary contract evaluator to creating a separate evaluator for each individual contract, and vary how and when contracts and results are communicated to provide programmers with myriad verification mechanisms that they may choose on a per-contract basis. Disney et al. (2011) utilize multiple contract verification processes to monitor and validate communications as (quasi-)recursive, long-running middle-man processes that mediate module interactions. In their system, contracts forward messages between these modules, inspecting constants and starting sub-monitors for structural contracts. This process separation uses isolation to ensure, a priori, contract non-interference. Our approach and focus differs in that we do not model "client" and "server" modules, instead presenting a user "program" interacting with contract processes directly; we do not require (or desire) non-interference in our contracts, allowing them to inspect modify, and otherwise manipulate contracted terms (e.g., by installing wrappers); and, finally, the focus of our work is allowing contracts to vary their method of verification the program on a per-contract basis and exploring these interaction patterns for programmer utility and semantic breadth.

Contracts as processes
Even so, these works share a notion of contracts as processes, suggesting that the temporal contracts outlined by Disney et al. (2011) may be directly encoded in λ ⇒ /c , and that the calculus presented by Disney et al. (2011) might be modified to support λ ⇒ /c -style multi-strategy verification (via adding delay operations and modifying their guard procedure) at the cost of some of their guarantees (such as non-interference).
programming language via four properties (meaning reflection, meaning preservation, faithfulness, and idempotence). They ultimately conclude that "faithfulness is better than laziness."  go further, classifying different approaches through observational equivalence, based on when and how they are enforced (with axes of static versus run-time and tight, loose, or shy-loose respectively). They also introduce the notion of a shy contract, which is analogous to the lazy verification presented by Degen et al. (2009): it is only allowed to inspect parts of the program that are evaluated at runtime. Degen et al. (2012) revisit different contract verification systems in call-by-need languages, evaluating them for completeness and meaning preservation, classifying a number of contract systems (Findler & Felleisen, 2002;Chitil & Huch, 2006;Hinze et al., 2006;Degen et al., 2009;Xu et al., 2009) by these properties. Conversely, our work follows Swords et al. (2015): instead of classifying verification strategies via secondary properties, we have presented a semantic-based comparison by directly encoding each verification strategy in λ ⇒ /c .

Alternative contract enforcement mechanisms
Our presentation uses runtime enforcement of contracts that all exist at the value level. This is not, however, the only enforcement mechanism available. Ou et al. (2004), Flanagan (2006), and Greenberg et al. (2010) each present a model of contract usage similar to refinement types (Freeman, 1994) tracking these contracts at the type level and enforcing (and accruing) them as values flow through these types. The work by Greenberg et al. (2010) in particular has recently been the subject of multiple extensions, including space efficiency (Greenberg, 2015), datatypes (Sekiyama et al., 2015), and stateful contracts (?). While manifest contracts are closely related to standard contracts (and Racket's chaperone and impersonator system (Strickland et al., 2012)), our work treats contracts as values instead of type-level terms. Xu et al. (2009) andNguyen et al. (2014) both present static verification models for contract verification, using static analyses to determine which contracts hold prior to running the program. While related, this style of static checking is orthogonal to our presentation here: we do not attempt to subsume static checking with our framework. Clojure's core.spec library (at https://clojure.org/guides/spec) also supports enforcing contracts in different ways. Unlike our multi-strategy approach, however, the spec library allows users to determine which programming phase to check the provided specification (or "spec") at: the spec written for the program may be used to both instrument the runtime (via software contracts) and to generate sample data to probabilistically check functions, etc., without running the program itself. Combining our multi-strategy approach with multi-phase checking remains as future work.
Finally, Shinnar (2011) presents contract assertions in the context of concurrency and software-transactional memory, focusing on effectful contracts interacting with Software Transactional Memory in Haskell, using Haskell's monadic effect system. They introduce the idea of delimited checkpoints for STM, allow memory changes to be observed and rolled back, supporting specification contracts alongside "framing" contracts, which ensure programs do not exhibit specific behaviors, and separation contracts, which combine these to provide separation logic-esque behavior. The rollback capabilities also allow the program to undo effects in the event of contract violations, and integrating such a mechanism into λ ⇒ /c may allow us to address the effect-related shortcomings of promise-based and concurrent contract verification. This integration remains as future work.

Complete blame and contract monitoring
Multiple approaches to runtime verification blame assignment have been proposed (Wadler & Findler, 2009;Greenberg et al., 2010;Ahmed et al., 2011;Dimoulas et al., , 2012, with special attention given to function contracts (and, in particular, dependent function contracts). Since our contract combinators are library functions, we can provide any blame assignment approach necessary (including indy semantics ).
Ensuring complete monitoring, defined by Dimoulas et al. (2012), in particular, introduce the notion of complete monitoring, a fundamental correctness criterion for contract systems that generalizes correct blame assignments by ensuring each value that moves between components is monitored. Unfortunately, proving this property for λ ⇒ /c is particularly challenging for a number of reasons. First, their definition relies on multiple modules. We would need to first extend λ ⇒ /c with a module model (likely as interacting processes, following Disney et al. (2011)). Second, Dimoulas et al. (2012) use an ownership-and-obligation model to define complete monitoring, and we would need to replicate this in a multi-process setting where we explicitly model each contract verifier as a separate entity. In particular, we would need to address how value ownership changes when transferring value to monitors and, further, when transferring results (e.g., when values flow through multiple monitors toward the answer, such as for pairs, or when a delayed reference is forced after being communicated). Finally, complete monitoring is tells us little about some strategies, including conc: complete monitoring only requires that the program terminates with a value, diverges, or correctly detects a contract violation, and, if every contract is enforced under conc, a λ ⇒ /c scheduler may always complete the user process without checking any outstanding contracts. This suggests that, in the context of "best-effort" contracts, complete monitoring is not a sufficient requirement.
Gradual Typing. Gradual typing uses runtime verification in the form of casts and coercions to ensure that values have the correct types as they flow through the program (Siek & Taha, 2006). Similar to contract verification, there are a variety of ways to enforce these properties and track blame (Herman et al., 2007;Wadler & Findler, 2009;Vitousek et al., 2014). Siek et al. (2009), in particular, provide a discussion of different approaches in the literature, recreating each by adding optional reduction rules to the same calculus and demonstrating how different sets of these reduction rules exhibit different behaviors. Our work here similarly compares the field of contract verification strategies, but we diverge in our use of processes, our direct encoding of each strategy into an underlying core calculus, and our strategies' coexistence in the same surface language. Applying our communicationbased approach to gradual typing remains as future work, but hints as the possibility of multiple error detection semantics existing together in the same language.

Conclusion
In this paper, we have presented a unifying framework for contract semantics, refining and extending the work presented in Swords et al. (2015). These extensions include a clarified semantic model that clarifies which process evaluates which part of each monitor, an explicit semantics for an fconc contract verification (thus demonstrating how new contract systems may use target λ ⇒ /c ), extended explanations of the previous work's multi-strategy approach to verification, and an extended presentation of metatheoretical results. There is a sample implementation of this work available for Racket at https://github.com/cgswords/ccon.

Future work.
There is still much to explore in strategy-based contract monitoring. At the beginning of this paper, we identified three dimensions in the design space that each allow for a gradient of answers and whose answers lead to further questions about the behavior of runtime contract verification. The strategies here are not a covering of the design space, but an exposure, all expressed in a single, canonical framework that can be extended and used to implement and compare other verification patterns. This approach also hints at potential strategy combinators, or "meta-strategies", that allow us to combine and modify verification behavior. Finally, this unified approach gives us room to ask how this work applies to other runtime verification systems, explore the relation between this style of monitoring and aspect-oriented programming, and investigate what lies beyond strategies.