Gradual Typing for Effect Handlers

We present a gradually typed language, GrEff, with effects and handlers that supports migration from unchecked to checked effect typing. This serves as a simple model of the integration of an effect typing discipline with an existing effectful typed language that does not track fine-grained effect information. Our language supports a simple module system to model the programming model of gradual migration from unchecked to checked effect typing in the style of Typed Racket. The surface language GrEff is given semantics by elaboration to a core language Core GrEff. We equip Core GrEff with an inequational theory for reasoning about the semantic error ordering and desired program equivalences for programming with effects and handlers. We derive an operational semantics for the language from the equations provable in the theory. We then show that the theory is sound by constructing an operational logical relations model to prove the graduality theorem. This extends prior work on embedding-projection pair models of gradual typing to handle effect typing and subtyping.


INTRODUCTION
Gradually typed programming languages are designed to support smooth migration from a lax to a strict static type discipline [Siek and Taha 2006;Tobin-Hochstadt and Felleisen 2008].Most commonly, gradually typed languages add a static type system to an existing dynamically typed language and allow for (1) safe interoperability between the languages and (2) semantic guarantees that adding types to existing programs only results in stricter type enforcement, and no other behavioral change.More generally, gradual typing has been applied to provide a spectrum of precision in other kinds of typing disciplines such as re nement typing or e ect typing [Bañados Schwerter et al. 2014;Lehmann and Tanter 2017], where the "dynamic" side is a statically typed language itself.
One particular presentation of e ects and e ect typing that is gaining popularity is e ect handlers [Plotkin and Pretnar 2009].Operationally, e ect handlers are resumable exceptions, code can "raise" an e ect operation, which will then be handled by the closest enclosing handler, which in addition to the exception data will also receive the continuation for the raising code that can be invoked to resume at the original point where the e ect was raised.E ect handlers provide an intuitive typed interface to delimited continuations, and can similarly be used to conveniently implement backtracking search, non-determinism, mutable state, and as a convenient interface to external system calls.E ect handlers have been implemented in a number of libraries and experimental languages, and more recently have been incorporated as a built-in feature into OCaml 5, and have been proposed as an extension to WASM [Brachthäuser et al. 2020;Contributors [n.d.]; Cooper et al. 2006;Kiselyov et al. 2013;Leijen 2014;Lindley et al. 2017;Sivaramakrishnan et al. 2021].
Designers of languages supporting e ect handlers, much like designers of languages with exceptions, are left with a choice of whether the type system should merely validate that the input and output types of e ect operations are respected, or if an e ect typing system should be employed to determine that a particular e ect can only be raised when the context is known to implement a handler for it.On the one hand, checked e ects allow programmers to easily reason about which e ects can be raised by subprocedures and ensure they are handled appropriately, rather than being caught by the runtime system and causing the program to crash.On the other hand, strict checking may necessitate large code changes when code is extended to raise new operations, and even in languages such as Java that support both checked and unchecked exceptions, unchecked exceptions are preferred in many scenarios.Furthermore, when adding e ect typing to a language that does not already support it, even correct existing libraries may not typically pass the necessarily conservative static type checker.It may be infeasible to rewrite large amounts of existing library code to precisely track e ect usage.Gradual typing provides a linguistic framework for designing languages where a programmer is not entirely locked in to one system or another: they might use unchecked exceptions in one module and checked exceptions in another, while supporting well-de ned interoperability with useful error messages at runtime if there is an e ect raised in a context where it is not expected.Further, a gradually typed language provides a path for gradually migrating code from less precise to more precise static type checking.This potential for gradual typing to be used in this way to incorporate e ect typing disciplines into existing languages has been eloquently discussed in prior work by Phil Wadler [Wadler 2021].
In this work we present the design and semantics of GrE , a gradual language with e ect handlers that supports gradual migration from unchecked e ects to precise e ect typing.The untracked sublanguage of GrE is designed to be similar to SML and Java's treatment of exceptions: new e ect operations are declared with speci ed input and output types, and these can be imported and used to raise and handle those operations in other modules, but which e ects are raised by a function is not tracked by the type system.In addition, GrE supports tracked function types → where the input values must be of type , output values will be of type , and the function may raise any of and only the e ects in the set .The untracked function type is modeled then as a type → ?which has a "dynamic" e ect type, in the sense that it may raise any e ect, possibly including unknown e ect operations declared in some independent module of the program.Since our main focus in this work is on providing a foundation for extending existing statically typed languages such as OCaml 5 with e ect types, we have chosen not to support full dynamic typing in the design of GrE .However, the design should easily accommodate supporting fully dynamic value typing in addition to the dynamic e ect typing using standard gradual typing techniques.
In GrE , new e ect operations can be declared in each module, just as new exceptions can be declared in Java and ML-style languages.When an e ect is declared in a module, it is given an associated request and response type.For instance, an e ect for reading a boolean state would be get : Unit --> Bool, the user provides a trivial value as the request and receives a boolean value Proc.ACM Program.Lang., Vol. 7, No. OOPSLA2, Article 284.Publication date: October 2023.
Gradual Typing for E ect Handlers 284:3 as the response, while an e ect for writing to boolean state would be set : Bool --> Unit.Similar to ML and Java, GrE takes a nominal approach to e ect operations: each e ect operation has an associated request and response type that are used to determine when an e ect is properly raised or handled.However, having a single, global assignment from e ect names to request/response types is problematic from the perspective of gradual migration from untracked to tracked e ects.In a completely nominal form of e ect typing, if an e ect operation is used in many di erent modules with imprecise typing, and one module is migrated to use a more precise version of the e ect's request/response type, then we would need to migrate all modules to use the more precise type.Instead, gradual migration should allow for this to be done a single module at a time.To achieve this, in GrE , we take a locally nominal but globally structural approach to the typing of e ect operations.That is, locally, within each module, the request and response type for an e ect are xed, and all raise and handle constructs are checked with the same typing.On the other hand, globally, di erent modules across the program can associate di erent types to the same e ect operation.At module boundaries, i.e., imports and exports, modules are statically allowed to interoperate if they agree on the precisely typed portion of the e ects they share.If one module is more precise than the other, then dynamic runtime monitoring is inserted in the implementation to ensure that the runtime behavior agrees with the static typing, raising an error if the dynamically typed code violates the imposed runtime type discipline.
There are two aspects in designing a sound gradually typed language: designing the syntax and gradual type checking of the surface language and designing the corresponding core language and semantics.The syntax should support a simple process for migrating from an imprecise to a precise style, satisfying the static gradual guarantee [Siek et al. 2015].We designed the surface language with the goal of modeling program migration from dynamic to static e ect typing.For this reason we include a simple module system in the style of Typed Racket [Tobin-Hochstadt and Felleisen 2008] so that we can express that di erent portions of the program have di erent views on how the e ect operations are typed.Once the design of the base language is xed, we design the gradual type checking using techniques from prior work to arrive at a gradual type system that satis es the static gradual guarantee [Garcia et al. 2016;Siek and Taha 2006].
Next, the core language provides a de nition for the runtime semantics.The semantics should admit useful type-based reasoning principles for precisely typed code, even in the presence of interaction with imprecisely typed components.Further, the aforementioned migration process should have a predictable impact on program semantics: migrating to more precise checking may result in new errors being identi ed (statically or dynamically), but otherwise should not impact program behavior, a property known as the dynamic gradual guarantee or graduality [New and Ahmed 2018;Siek et al. 2015].To design the core language and runtime semantics, we follow the prior work ([New and Ahmed 2018;New et al. 2020New et al. , 2019]]) which established a recipe for designing a new gradual core language to satisfy the graduality theorem and validate strong typebased equational reasoning principles.Their approach is to axiomatize the type-based reasoning principles as equations and the graduality theorem as inequalities, where casts are de ned not by specifying their operational behavior a priori but instead by assuming they are given by least upper bounds/greatest lower bounds.Then the operational behavior of the casts can be derived from the inequational theory.An operational or denotational model must then be constructed to prove the theory is consistent, which implies the graduality theorem.But since the operational semantics is derived from the inequational theory, this also establishes a stronger theorem that the observable behavior of the casts is uniquely determined by the desired type-based reasoning and graduality, showing that any observably di erent cast semantics must violate one or more of the axioms.
For designing our core language, called Core GrE , we extend this recipe, which previously been demonstrated on simple and polymorphic types, to apply also to e ect casts and subtyping of value and e ect types.We then show that every rule of an operational semantics is derivable from the least upper bound/greatest lower bound speci cations of casts as well as congruence rules and an e ect forwarding principle for handlers.The e ect forwarding principle states that a handler clause that simply re-raises the e ect it handles with the same continuation can be removed without changing the observable behavior of the system, an intuitive principle as well as a highly desirable compiler optimization.
In this work, we extend prior step-indexed logical relations models for proving graduality to handle e ects and subtyping, by showing that the runtime casts satisfy the properties of being embedding-projection pairs [New and Ahmed 2018].In doing so, we show how to combine e ect and value embedding-projection pairs within the same system, and how they interact.Additionally, we identify new semantic principles for the interaction between subtyping and runtime casts.
The contributions of the paper are as follows: (1) We de ne a gradually typed language GrE supporting migration from unchecked to checked e ects and handlers.
(2) We prove this language satis es the static gradual guarantee and the dynamic gradual guarantee (graduality).
(3) We give the language a semantics by elaboration into a core language, core GrE .(4) We axiomatize the desired graduality and program equivalence properties of the core language by giving an inequational theory.We then derive from this an operational semantics by orienting certain equations in the theory, showing that the operational behavior is derivable from the graduality and extensionality principles.(5) We prove type soundness and graduality by constructing a logical relations model, extending prior work on embedding-projection pair semantics to e ects and subtyping.

OVERVIEW OF GREFF
Before discussing the syntax and semantics of GrE , we provide an informal introduction to its features and how it supports a gradual migration from unchecked to checked e ect handlers.As an example, consider the implementation of a simple threading library using e ect handlers.We start with a system using unchecked e ect types in an ASCII syntax in Figure 1.We split this program across three modules: rst, a module Operations de nes the e ects we will be using in our other modules.These are the e ects that the threads use: print for displaying output so that we can observe the interleaving of threads, yield, which yields back control to the scheduler, and most importantly, fork, which allows for a thread to spawn new threads.Each e ect declaration effect e : Req --> Resp is annotated with two types: the type of requests to the ambient handler, and the type of expected responses from the ambient handler.For instance, the request type for print is a string to be printed, and the response is unit.In a more realistic setting, the response type might be a boolean to say if the printing succeeded, or an unsigned integer to say how many bytes were successfully printed.For yield, the request and response are both unit.For fork, the response type is again unit and the request type is a thunk 1 -[?]-> 1 where the ? is the type of e ects the function may raise when called.In this case, ?indicates the thunk might raise any e ect.Next, module Scheduler de nes a scheduler as a handler for the provided e ects.For simplicity the implementation relies on some built-in list implementation, and shallow handlers, a simple extension to our formalism which uses the more complex deep handlers.The scheduler loop takes a queue of threads, represented as thunks, and runs them in a round-robin fashion, taking in a string consisting of everything printed so far and returning a nal string that contains everything printed by running the threads.If there are no threads in the queue, the scheduler returns the string unchanged.Otherwise, it pops o the rst thunk in the queue and executes it, handling e ects module Operations where effect print : str --> 1 effect yield : 1 --> 1 effect fork : (1 -[?]-> 1) --> 1 module Scheduler where import Operations.print: str --> 1 import Operations.yield: 1 --> 1 import Operations.fork: (1 -[?]-> 1) --> 1 define sch-loop : List (1 -[?]-> 1) -[?]-> str -[?]-> str = lambda q. match q with empty -> lambda s. s cons(thunk, q') -> shallow-handle thunk() with ret _ -> sch-loop q' print(s, k) -> lambda s'.sch-loop (cons k q') (s ++ s') yield(_, k) -> sch-loop (snoc q' k) fork(new,k) -> sch-loop (cons k (snoc q' new)) define scheduler : (1 -[?]-> 1) -[?]-> str = lambda thunk.
sch-loop (cons thunk empty) "" module Main where import Operations.print: str --> 1 import Operations.yield: 1 --> 1 import Operations.fork: (1 -[?]-> 1) --> 1 import Scheduler.scheduler: (1 -[?]-> 1) -[?]-> str define letters : 1 -[?]-> 1 = print("a"); yield(); print("b"); () define numbers : 1 -[?]-> 1 = print("1"); fork(letters); print("2"); () define main: 1 -[?]-> str = scheduler(numbers) that it raises.The ret clause handles the case that the thread terminates without performing any e ects, in which case, the scheduler executes the remaining threads in the queue.In the print(s,k) clause, the s parameter is the str to be printed by the thread, and the k is the continuation for the program point where the print was raised.The scheduler handles this case by taking in the string accumulator, appending the printed string to the back of it, and continuing the scheduling with the continuation k at the front of the queue.In the yield(_,k) clause, the scheduler continues with the continuation at the back of the queue.Finally, in the fork(new,k) clause, the scheduler continues with the continuation thread k at the front of the queue and the new thread new at the back of the queue.Then this loop is run by a wrapper scheduler function which calls the scheduler loop with a singleton queue and an initial empty string accumulator.Finally, we have the Main module, which uses the scheduler de ned in the Scheduler module with a thunk that uses the e ects de ned in the Operations to implement a program that prints a simple message using threads whose output will depend on the scheduler's behavior.
The they import a function from another module.With e ect typing, this information can be expressed precisely using e ect annotations on the functions themselves.For instance, in the declaration of the fork operation, the request is a thunk that when launched as a thread itself may raise further e ects such as manipulating shared state, yielding to other threads, or forking additional threads.However with imprecise e ect tracking, the scheduler procedure has the uninformative type (1 -[?]-> 1) -[?]-> 1 so we cannot specify in the type which operations the scheduler will handle and which it will propagate forward.GrE allows as well for the introduction of precise e ect types to express these choices in the type structure.In gure 2, we show a fully precisely typed version of the same threading program (with implementations, which are unchanged, now elided).This allows us to specify in the Scheduler module that the scheduler expects threads that can (1) print a string, (2) yield to the other threads and (3) fork further threads with the same e ects.To express this, the scheduler module changes the type to (1 -[fork,print,yield]-> 1) -[]-> str expressing that the scheduler will be passed a thunk that may fork, print or yield, but will itself return a string without raising any e ects.Additionally, we can express that forked threads should only raise these three e ects as well.This is expressed by annotating the import statement, which de nes fork as a recursive 1 e ect type whose response type is trivial and whose request type is that of thunks that can raise the three provided e ects.This typing will then be used by all occurrence of the fork e ect, in raise or handlers, within this module.The types are also changed in the main module, where the letters thunk can be given a type expressing it only prints and yields, whereas numbers thunk only forks and prints.These are compatible with the types in scheduler using an e ect subtyping that allows functions that use fewer e ects to be used in a context that can handle more.
Since GrE is a gradual e ect language, a programmer who started with the imprecise program does not need to fully type the entire program before running it.Instead, the programmer can gradually migrate from the imprecise style to the more precise style, for example one module at a time.In fact, any of the 2 3 = 8 combinations of the imprecise versions and precise versions of the three modules presented here will pass the GrE gradual type-and-e ect checker.For instance, we might start with adding precise e ect typing to the Operations module to specify the e ects that a forked thread can have.Whereas in a non-gradual type system, this would require changing the consumer modules to use the more precise typing, in GrE , the import statements allow for the uses within the module to continue to use the imprecise typing, and at the module boundary it is checked that the precise components of the declared type for the fork e ect match the precise components of the declaration in the de ning module.On the other hand, we can keep the Operations module imprecisely typed, and instead add typing to the Scheduler module rst.This is again unusual compared to a conventional typed language, we have declared a nominal e ect type in one module, but used it at a di erent type in a client module.The import statements allow for the gradual migration of the client code without changing the original library.
The module system plays a crucial role in allowing for the programmer to independently choose between migrating the declaration site of the nominal e ects and its uses.If we were in a purely expression-oriented language, then any change to the e ect declaration, even in a gradual language, would change the typing of all uses of the e ect.Here we use the module boundaries in the style of Typed Racket as a way to formally specify di erent expectations of what the type of the nominal e ect operations should be in di erent portions of the codebase.This design xes the types of the e ects within a module, in keeping with the common nominal type system for exceptions in the ML family of languages.An advantage of this design is that it is clear to the programmer at all times what the type of an e ect is in an expression.Further, this makes it clear what migration of e ect types means: the programmer can independently change the precision of the e ect types for each module one at a time, and there is never any confusion about what the "current types" of an e ect is.
However note that it is not the case that the only gain or loss of precision happens at module boundaries.Within a module, gradual type casts of function values can occur.For instance, if you pass a value of type A -[ ?]-> B to a function that expects an input of type A -[ fork ]-> B then a downcast will be inserted to ensure only fork e ects are raised.

SURFACE AND CORE GREFF
In this section, we introduce the syntax and typing of GrE along with its elaboration into a core language, Core GrE .GrE includes a module system and nominal e ect operations, as well as a gradual type checking algorithm that allows for a mix of dynamic and static e ect tracking.Core GrE , on the other hand, is a simpler expression language with a declarative type system where all gradual type casts (but not subtyping) are explicit in the term.The high-level features of GrE are elaborated away into core GrE .Because Core GrE is simpler, we describe its syntax and typing rst, and then describe GrE and its type-checking/elaboration algorithm.

Syntax and Typing of Core GrE
We give an overview of the Core GrE syntax in Figure 3. Core GrE expression syntax include typical lambda calculus syntax for variables, let-bindings, functions and booleans.Additionally, there is a term ℧ that represents a runtime error produced by a failed cast.Next, it includes forms for raising an e ect operation raise ( ) and handling e ect operations handle {ret .| }.
We use to stand for an element of some xed countable set of e ect names.The handler includes a clause ret .to handle a return value for as well as clauses for handling e ects .Abstracting from syntactic details, is modeled as a nitely supported partial function (written ⇀ n ) from e ect names to terms, which all have two free variables and for the payload of the e ect raised and its continuation.That is, if syntactically a handler has a clause ( , ) ↦ → , we model this by having ( ) = .Next, Core GrE includes four explicit gradual type cast forms: downcasts (⟨ ↞ ⟩ ) and upcasts (⟨ ↢ ⟩ ) for value types, as well as analogous casts for e ect types (⟨ ↞ ⟩ and ⟨ ↢ ⟩ ).
The value types , , classify runtime values: in this simple calculus, just booleans and functions, where functions are typed with respect to a domain, codomain as well as an e ect type which classi es what e ects the function may raise when it is called.The e ect types are either ? to indicate dynamically tracked e ects, or a concrete e ect type.A concrete e ect type says which e ect names can be raised, and when they are raised, what is the type of the request the raising party provides and what is the type of responses with which the handling party can resume.Abstracting from syntactic details, this is de ned to be a nitely supported partial mapping from names to pairs of value types (i.e., an element of the Cartesian product ValueType2 = ValueType × ValueType).To model that an e ect can be raised with request type and response type we would de ne ( ) = ( , ), which we will notate more suggestively as : ∈ .As shown in Section 2, programs declare which e ect names can be used, and with which associated request and response types.To track this information in typing core GrE expressions, we type check all GrE expressions against a Signature Σ which associates a pair of non-tracking types to each name.By a non-tracking type ?, we mean a value type that only use ?e ect types.Additionally, expressions are typechecked with respect to an ordinary typing context Γ.Finally, we de ne typical notions of value and evaluation context to encode a call-by-value, left-to-right evaluation order.Most notably, all casts are evaluation contexts, and function casts are values, i.e. "proxies" that delay type enforcement until an application is performed.
The use of non-tracking types in the signature is a design decision in the semantics of GrE : it means that when an e ect is declared in a module, it fully speci es only the non-e ect typing portions of the request and response types.When a module imports an e ect, it is only checked that the new request and response type are consistent with the exporting module.Since e ect types can be re-exported and the consistency relation is not transitive, this means that in general the types used in one module will not be consistent with those of the module where it was originally declared.However, transitive closure of consistency 2 does ensure that the types have the same non-tracking portion, and so it is sensible to de ne the valid instances of the e ect type to be any that agree on this non-tracking portion of the type.An alternative would be for the signature to have a fully speci ed type and limit all uses of the e ect to be at least as precise as the original declaration.However we argue that this is not in the spirit of gradual typing: for instance it might be the case that module provides an e ect declaration, module is an intermediate that re-exports the e ect and module is a client of that uses the e ect but does not directly interact with .Say , , all initially use untracked e ects, but then becomes typed and so speci es precise e ect typing for the e ect.The program functions properly and eventually is additionally made more precise but in such a way that the e ect implementation is incompatible with the usage in .In GrE this does not lead to a static error, because and are not directly communicating along a precisely typed interface, but rather through an intermediary that uses imprecise typing.Indeed, it may be the case that uses the e ect di erently between and and there is no runtime type Terms , :: error.However, if becomes precisely typed, it must specify its interpretation of the e ect and will result in a static error with either or .
Next, we present declarative term typing rules in Figure 4.The main judgment Σ | Γ ⊢ : says that under the assumptions Γ, can raise e ects drawn from , and produce a nal value of type .We follow the convention that whenever we form the judgment Σ | Γ ⊢ : we must already have established that the types in Γ, , are well-formed under the signature Σ.First, we include a subsumption rule for value and e ect subtyping, which we will soon de ne.The rules for value forms (variable, booleans, and lambdas) all have an arbitrary e ect type because they do not raise any e ects themselves.The runtime cast error ℧ can be given any value or e ect type.The let, application and if rules simply require that all the sub-terms use the same e ect type, though subsumption can be used to combine e ects.The raise rule says that the e ect being raised needs to be in the current e ect type and the payload of the request must also have the same e ect type.
Next, the rule for typing a handler works as follows.First, the output value type is and output e ect type is , while for the scrutinee the corresponding types are and .First, we check that the return clause has the same output types as the handler overall, when its input has the type of the output of .Next, for each e ect operation : raised by , either the e ect is not handled by , in which case it must be included in the nal e ect type, or it is handled by .If it is handled by , then the clause ( ) must be well typed with a request value : and a continuation that takes responses and has output e ect and value types that match the term overall : → .Lastly, we include the rules for type and e ect upcasts and downcasts.Whenever a type precision relationship ⊑ holds (to be de ned), we get an upcast from the more precise type to the more imprecise type and a corresponding downcast from to .Finally, nishing out the syntax, in Figure 5, we de ne three judgments on types: well-formedness, subtyping and type precision.Well-formedness Σ ⊢ and Σ ⊢ checks that the types used in e ect operations erase to the types associated in the signature.Here we use the notation | | to mean the erasure of e ect typing information in that we replace any e ect type subterms with dynamic ?. Subtyping works as usual for booleans and functions, contravariant in domain of the function type, but covariant in the codomain and e ect.Subtyping for e ect types includes both a width subtyping aspect: a smaller type can raise fewer operations, as well as a depth aspect that is Finally, type precision ⊑ tracks instead how "dynamic" or "imprecise" a type is.For functions it is covariant in every argument, and for e ect types, the dynamic e ect is the most imprecise and for two concrete e ect sets, it has a depth rule that that is covariant in request and response positions.In a more standard gradual language with full dynamic typing, in addition to the dynamic e ect type we would have a dynamic value type ?that is similarly maximally imprecise among value types.

Syntax and Elaboration of GrE
We present the syntax for the surface language GrE in Figure 6.To distinguish surface GrE syntactic forms from similar core GrE forms we use an underscore of for surface GrE forms.
A GrE program consists of a sequence of modules ending in a single "main" module.Each module consists of two parts: rst, the e ect de nitions and then the value de nitions, whose types annotations may use the e ects previously de ned in that module.An e ect de nition is either a declaration of a new e ect operation effect : or an import of an existing e ect operation import-eff .: . In either case, the declaration includes the request type and the response type for the e ect within the current module.An e ect import brings an e ect de ned in another module into the current scope, but with a possibly di erent request and response type.To support gradual migration, these types are allowed to have a di erent level of precision than the original, but on subterms where both types are precise they must match.After the e ect declarations are the value de nitions which are also either a de nition of a new value define = or an import of a value declared in a di erent module at a possibly di erent type | Γ , : Fig. 6.GrE Syntax import-val as : .For simplicity, all e ects and values are public and can be imported by later modules.Finally a program ends with a main module, which consists of the same kind of e ect and value declarations, followed by a nal main expression.Surface GrE types di er from core GrE types in that e ect types are nominal: a concrete e ect set is simply a nite set of names such as {fork, yield, print} where the types of the e ect names are determined by the declaration in the current module.The elaboration process adds the relevant type information to match the more structural typing of core GrE .Surface GrE terms and values are for the most part similar to the core GrE forms except that they may include syntactic type annotations in order to support the algorithmic gradual type system of the surface language.Finally we de ne the typing contexts for programs Δ and modules Γ , which are used in the elaboration/type checking process.A program typing context Δ associates module names to their module typing contexts.The module typing context Γ contains both typings for values and e ect names.Note that the types in the module typing context are core GrE types because these types are the result of elaboration of surface GrE types.
Next, we present the combination type checker and elaborator from GrE into core GrE .We view GrE programs as essentially a description of an e ect signature Σ and a closed expression well-typed under that signature.The module system is a way to manage the declaration of new e ect operations in the signature and a way to manage the typing of e ect operations by giving nominal associations to request and response types rather than solely the structural typing in core GrE .We describe the elaboration of the module language in Figure 7.The top-level judgment : says that under the starting signature Σ and previously de ned modules Δ, we can elaborate to a core GrE term with core GrE e ect type and core GrE value type that is well-typed under the extension of the signature by Σ ′ .To elaborate a complete program, we initialize this with empty signature and module typing (• | • ⊢ ⇒ Σ ⊢ : ).This expresses that not only does a program denote a core GrE program, but it also has a "side e ect" of allocating new e ect names Σ ′ .A module is elaborated with the judgment Σ | Δ | Γ ⊢ ⇒ Σ ′ ; ′ ; Γ ′ .The outputs of this judgment are the newly allocated e ects of the module Σ ′ , the names of e ect operations and types for values the module de nes Γ ′ and the de nitions of all the values the module de nes, given as a core GrE substitution ′ from variable names in Γ ′ to core GrE values of their associated types.Then to elaborate a program consisting of several modules, we rst elaborate the modules and then elaborate the remainder of the program and nally combine the two by let-binding all of the variables declared in the module, which we write as a shorthand let Γ = in .A module is elaborated by combining the results of elaborating each declaration.A new e ect declaration checks that the name is not previously declared, and then recursively elaborates the syntactic types declared for request and response and then adds these to the allocated e ects as well as the local e ect names declared in the module.When adding to the signature, we take erasure of the types because signatures use untracked types.Next, to import an e ect from a di erent module, the types given for the e ect are checked to be compatible with the types declared in the other module.
Note that for simplicity of presentation, all e ects must be used with the same name in all modules.
More exible renaming mechanisms can easily be supported in a realistic implementation.Here the compatibility judgment ∼ ′ is de ned on core GrE types as the conjunction of gradual subtyping in both directions, ≲ ′ and ′ ≲ , to be de ned soon.This compatibility check ensures that any imports from that module using this e ect name will succeed.We check gradual subtyping in both directions because the e ect may be used in both positive and negative positions in a later import.This e ect name is added to the local names only, and not the signature, because it is using an already allocated e ect name.Next, de ning a value simply elaborates the value and adds its type to the output typing and associates the value to that name.Importing a value is similar, except that we check that the declared type is a gradual subtype, and so can be coerced by the cast ⟨ ⇐ ′ ⟩, whose de nition will be described shortly.
Next, we de ne the elaboration of the expression language in Figure 8.The judgment Γ ⊢ ⇒ : ! says that under the typing of names given by Γ , the GrE expression elaborates to the core GrE function , which will be well-typed with inferred core GrE e ect type and value type .All forms essentially elaborate to similar forms in core GrE , but with suitable casts inserted.First, we de ne the translation of value type casts ⟨ ⇐ ⟩ and e ect type casts ⟨ ⇐ ⟩ as an upcast followed by a downcast.For the e ect cast, these casts go through the dynamic e ect type, but for two value types there is no single most dynamic e ect type so we again use the erasure operation.Note that this will only be well-typed in case | | = | |, which is ensured whenever ≲ , which is a precondition for inserting a cast.This is not necessarily the most e cient implementation of the cast, we discuss optimizations in Section 4.3 Next, variables, boolean values and function values elaborate to themselves with an empty e ect type ∅.The let-binding form shows how di erent e ect types are combined: the e ect types of and are combined using a gradual join ∼ ∨ (to be de ned shortly), and casts are inserted into the elaborations of and to give them this e ect type.The ascription forms simply check that the appropriate kind of type satis es a gradual subtyping judgment and inserts a cast.This uses the elaboration of types Γ ⊢ ⇒ , de ned below.The if rule checks that the condition has boolean type and gives the output value type as the gradual join of the branches, and the output e ect type as the gradual join with the condition expression as well, matching prior work [Garcia et al. 2016].The application rule is similar except that the argument is cast to have the type of the domain of the function and the e ect type of the function is joined with the e ect types of the terms.Next, we have the raise form, which elaborates to a raise but rst let-binds the request term and casts the raise term to have an e ect type that is the join of the request term's e ect type and the operation's type.Finally, we have the most complex case, the handle form.The handle form elaborates to a handle form in the core language with casts inserted in each case to make them agree with the ascribed value type and e ect type .The request variables and input to the continuations are given by looking up the e ect in Γ , while the output is given by the ascription.The most complex part of this elaboration is the cast needed for the scrutinee .In the core language, we need that all of the e ects that raises but are not caught by the handle are in the output type .But when is dynamic and has concrete e ect type or vice-versa, this is not necessarily true, so in these cases a cast must be inserted that e ectively handles all of the "other" e ects.This de nition is given below in a special elaboration of handle scrutinees (Γ ⊢ handleTy( , , ) = ).Here, the type is the elaborated type of the scrutinee, is the elaborated type of the result of the handle expression, and is the set of e ect names caught by the handler, where we write Γ( ) for the map that looks up the currently associated types for each operation in .First, if and are both precise collections of e ects, then we check that all of the e ects it raises are either caught or still occur in the output type, and we insert a subtyping cast.Second, if , the type of the scrutinee is imprecise, then we downcast it to include only the union of the output e ects and the caught e ects, otherwise erroring.Third, if the scrutinee is precise but the result = ?is dynamic, then we need to upcast all of the unhandled e ect operations to their dynamic versions.This is expressed by having the result type be the combination (⊎) of the e ects who are handled as is, written | with the most dynamic version of any other e ects that are not handled |Γ(dom( ) − )|.Here | means the restriction of the partial function to only be de ned on the set .Finally, if the scrutinee and the are both imprecise then we put a trivial identity cast to ? on the scrutinee.
Finally, Figure 9 describes the elaboration of types, gradual subtyping and gradual join and meet.Value and e ect type elaboration Γ ⊢ ⇒ is mostly structural.The elaboration of a concrete e ect set is essentially a "map" over the elds of the concrete e ect set, saying the elaborated concrete e ect type has the exact same names as the surface e ect set, and they are associated to the request and response types of the e ect operation based on the current module context Γ .Next, we describe the mostly standard gradual subtyping of value types ≲ and e ect types ≲ to determine when a dynamic cast ⟨ ⇐ ⟩ or ⟨ ⇐ ⟩ would reduce to subtyping on the precise portions of the types.Note that we de ne gradual subtyping of types in the core language i.e., after elaboration, so that we can compare e ect types across module boundaries that use di erent typings for the e ect names.With this intuition, the de nition is like that of subtyping, except that the dynamic e ect type is a gradual subtype and supertype of all other e ect types.
Lastly, we de ne gradual join and meet of types and e ects as a partial function.The gradual join of types is de ned similarly to prior work, with the covariant positions in the function type recursively being joined, while the contravariant position, the domain uses the gradual meet.The gradual join of two concrete e ect rows takes the union of the e ects used in each type, where the common e ect names have to be joined as well.Here the request is covariant, and recursively joined and the response type is contravariantly and so recursively the gradual meet is used.On concrete e ect types, the gradual meet is similarly de ned as an intersection of the e ects used, where the requests and responses are handled dually.Finally, taking the gradual join with the dynamic e ect always returns the dynamic e ect and taking the gradual meet always returns the original type.This can be justi ed by the AGT methodology by interpreting the concretization of the gradual e ect type as the set of all possible fully static e ect types.Following the AGT methodology in this way ensures the static gradual guarantee is satis ed.
We conclude by noting the following syntactic properties of elaboration, which follow by structural induction.

AXIOMATICS AND OPERATIONAL SEMANTICS
Next we turn to the semantic aspects of GrE : how expressions are evaluated, what simpli cations/optimizations are correct to perform, and that the graduality principle holds for the language.
We formalize these three aspects axiomatically in the form of an inequational theory for reasoning about Core GrE programs.That is, we de ne a notion of inequality ⊑ between expressions called term precision, which is a kind of extension of the notion of type precision to expressions.
The semantic interpretation of this inequality is that has the same behavior as with respect to output and termination, except in that it may raise a dynamic type error when does not.From this notion of inequality we get an induced equivalence relation ≡ that speci es when and have the same behavior.Term precision and the induced equivalence are used to model our desired semantic ideas: an expression can be evaluated to a value when the equivalence ≡ holds, can be simpli ed/optimized to when ≡ holds, and the graduality principle states that when is rewritten in the surface language to some ′ that has more precise typing information, then a corresponding relationship ′ ⊑ should hold: adding more precise type information results in more precise dynamic type checking.With this in mind, we axiomatize the valid optimizations known from e ect handlers as well as desired inequalities from prior work on graduality in our inequational theory.
Axioms are only useful if we can construct models in which they are satis ed.For GrE , we do this by constructing an operational semantics that speci es more precisely how to evaluate programs and then de ne notions of observational equivalence and an error ordering to model ≡ and ⊑ and prove that all of the axioms are valid in this operational model.We will construct this operational semantics, based on the axiomatics: we show in Section 4.2 that every reduction ↦ → is justi ed by a provable equivalence ≡ in the inequational theory.For many rules this is very straightforward, e.g., reduction of functions is justi ed by a corresponding equation.The most utility we get from the axioms in this case is for the cast reductions: cast reductions for handlers are justi ed not by a direct corresponding rule in the axioms, but instead by extensionality ( ) principles for handlers combined with a least upper bound/greatest lower bound property of casts identi ed in prior work as being key to the graduality property [New and Licata 2018].This shows that the operational behavior we de ne has a canonical status: if certain optimizations for handlers are to be valid, and the graduality property is desired, then the cast reductions we de ne must be used.

Axiomatics
We present a selection of the rules of the inequational theory of term precision in Figure 10.The full rules are provided in the appendix [New et al. 2023].The form of the inequality judgment is Γ ⊑ ⊢ ⊑ ⊑ : ⊑ , which says that is more precise, or, roughly, "errors more" than .This is a kind of heterogeneous inequality relation in that and are not required to have the same type: must have value type and e ect type and must have value type and e ect type under the context Γ ⊑ and ⊑ and ⊑ must hold.We allow for and to be open terms, typed with respect to the typing context Γ ⊑ .The typing context Γ ⊑ is like an ordinary typing context Γ, except that variables are typed : ⊑ where the left type is the type has in the left term and is the type for .For the context to be well formed, each of the ⊑ must be provable.
First, we add re exivity and transitivity rules, where in the transitivity rule both the value and e ect type are allowed to vary simultaneously.Next, we give two rules for modeling errors: rst ℧ is the least element in the ordering, which models the graduality property, and second that all evaluation contexts are strict with respect to errors.The latter uses equivalence ≡, which is de ned as a shorthand: ≡ means that both ⊑ and ⊑ are true.In this case, we elide the typing, but both sides are assumed to be well typed under the same context and typing Γ ⊢ , : .Next we have computation ( ) and reasoning ( ) rules for each type.For functions and if, these are standard call-by-value rules, so we instead show only the handle rules.There are two rules for handle.If the term being handled is a value, then the return clause is used.If the term being handled is a raise of an e ect , it is equivalent to the handler clause ( ) where the continuation is the captured continuation surrounding the original handler term.We require this to be a let, but note that we have additional rules that imply that any evaluation context that doesn't handle can be re-written as a let.We then have two reasoning ( ) rules for handle.First, if is handled by a handler with no e ect clauses, then the handler is equivalent to a let-binding.This can be combined with standard rules for let binding to show that any term is equivalent to a handler with no clauses ≡ handle {ret .| ∅}.We call this the non-handling principle.Second, we have a rule that says that any clause that simply re-raises its operation with the same continuation it was passed can be dropped from the handler, as this is the same behavior as not catching the term at all.We call this the e ect forwarding principle, as it says that forwarding an e ect to the ambient context is equivalent to not handling it explicitly at all.Combined with the non-handling principle, any term with e ect type can be shown equivalent to handle {ret .| } where simply forwards all the e ects in .We next show rules describing the interaction of subtyping with value type casts, the full system includes analogous rules for e ect types.The rst says that an upcast followed by a subtyping coercion is less than a subtyping coercion followed by an upcast, and the downcast rule is similar.Finally, we have rules specifying the behavior of value and e ect casts.These rules characterize upcasts as least upper bounds and downcasts as greatest lower bounds.The rst rule shows that the downcast is a lower bound and the second that it is the greatest.The upcasts have similar rules, and we include analogous rules for e ect casts as well.These lub/glb properties are adapted from prior work on axiomatics for gradual typing [New et al. 2019], but now incorporate the ordering on both e ect and value typing.We found that this general form of the rule, where the e ect is allowed to di er ( ⊑ ′ ) while performing a Γ ⊢ : 10.Inequational Theory value cast, is essential for proving the commutativity of value and e ect casts, which is used in the derivation of the operational semantics and also valid in our logical relations model.

Operational Semantics
Next, we show a selection of the rules of the operational semantics ↦ → ′ in Figure 11, eliding the standard call-by-value rules for booleans, functions and let-bindings.We capture the left-to-right, call-by-value evaluation order by using evaluation contexts de ned in Section 3.1.First, we have the rules for handlers.When a handler encloses a value, we execute the return clause.When a raise occurs, we search for the closest enclosing handler that handles the raised e ect and capture the intermediate evaluation context in the continuation passed to the appropriate handler.We capture this with the relation ′ # which says that the evaluation context does not handle the given operation.
The next rules concern the behavior of e ect casts.First, all e ect casts are the identity on values.Next, when upcasting a raise, we re-raise the e ect, but upcast the request and downcast the response according to the types in the output e ect type.An e ect downcast works dually if the e ect occurs in the result e ect type.However, if the e ect does not occur in the output e ect type (which can only occur if the input e ect type is ?), then an error is raised.Finally, we have the function downcast.Recall that a function cast applied to a value itself is a value, and only  reduces when applied to a value.When this occurs in a downcast, as shown, the result reduces to applying the original function to an upcasted version of the input and downcast of the output, where this time we cast both value and e ect types.Note the order of the value and e ect casts on the output is arbitrarily chosen: because value casts only a ect values and e ect casts only a ect e ect operations, the two possible orders are equivalent.The elided cast for function upcasts is precisely dual, and nally there is a trivial cast rule for the identity cast on booleans.We conclude the operational semantics with the following theorem, which establishes that the operational rules are all valid equational reasoning principles in any system that models the inequational theory.
The full proof is in the appendix [New et al. 2023], but we give an overview of how the behavior of e ect casts ⟨ ↞ ′ ⟩ is derived in particular.The core of the argument is to show that the downcast is equivalent to a particular handler, and then derive the operational reductions from the reductions for handlers.The handler is ⟨ ↞ ′ ⟩ ≡ handle {ret .| ⟨ ↞ ′ ⟩ } where the ⟨ ↞ ′ ⟩ handles precisely the e ects in ′ and for each such : ′ ′ ∈ ′ , the handling clause is de ned as That is, if the e ect is not present in , the handler errors, and otherwise it re-raises the e ect to its context with mediating casts.The raising party raises a request value of type ′ and expects a response of type ′ , but the ambient handler expects requests to have type and Then we apply congruence for handlers, with the cases of the right-hand side that handle e ects not in being irrelevant.Then the remaining clauses are all of the same syntactic structure except for upcasts and downcasts, and so the proof follows by congruence and the upcast/downcast rules.To show handle {ret .
′ ⟩ , we rst apply the downcast right rule to eliminate the cast on the right.Then to show handle {ret .| ⟨ ↞ ′ ⟩ } ⊑ we again use the e ect forwarding principle to rewrite the right-hand side as handle {ret .| ′ }.We again apply handler congruence, with the cases where ∈ analogous to the prior argument.In the remaining cases ⟨ ↞ ′ ⟩ ( ) ⊑ ′ ( ) where ∉ , we have the left hand side is an error, and so the argument follows by the fact that the error is the minimum in the ordering.
While the converse of Theorem 4.1 is not literally true that equivalent terms reduce to each other operationally, the graduality proof in Section 5 does imply that if ≡ then and are contextually equivalent with respect to the operational semantics.

Subtyping, Gradual Subtyping and Coercions
The elaboration de ned in Section 3.2 inserts casts of the form ⟨ ↞ | |⟩⟨| | ↢ ⟩ when a gradual subtyping ≲ is used in the type-checker.If we think of | | as the type of programs in the untracked language, this says to cast a program from one type to another, we should cast it to an untracked type and then to the other e ect-tracking type, similar to prior work on cast calculi based on upcasts and downcasts [New and Ahmed 2018].This is a reasonable cast if we think of the untracked language as our "operational ground truth", and so we should prove that any other translation is extensionally equivalent to this one.However, operationally, this can be quite a wasteful translation, as a cast can result in proxying at runtime, while subtyping coercions have no runtime behavior, and so are zero cost.For instance, if ≲ is true because in fact ≤ , then there need not be any runtime cast at all.For this reason, we would prefer to optimize the cast based on the subtyping information in the proof of ≲ .Since may be more imprecise than in some subterms and vice-versa, the structure of the cast should still be an upcast followed by a downcast, but with the possibility that we use implicit subtyping coercions at some points.There are three places we might insert the implicit subtyping coercion: before the upcast, between the upcast and downcast and after the downcast.From the proof of ≲ , we can extract types and subtyping/precision derivations as in Figure 12.
On the left we have a "pure subtyping" component of the gradual subtpying proof coming from , and on the right we we have the pure subtyping component coming from .In the middle we have two "dynamic" types also related by subtyping.There are then three paths from to in this diagram, which generate three di erent potential casts with implicit subtyping coercions ensuring they are well-typed as taking to : (1) Up and then right twice ⟨ ↞ ℎ ⟩⟨ ℎ ↢ ℎ ⟩ (2) Right, up and then right: ⟨ ↞ ℎ ⟩⟨ ↢ ⟩ (3) Right twice and then up: ⟨ ↞ ⟩⟨ ↢ ⟩.Fortunately we can choose whichever is operationally preferable: each of these casts is equivalent as a function from to and they are all equivalent to the ground truth cast ⟨ ↞ | |⟩⟨| | ↢ ⟩.The above discussion applies equally well to e ect casts, which are even simpler in that the "ground-truth" always factors through the single most imprecise e ect type: the dynamic e ect type.

SOUNDNESS AND GRADUALITY
In this section we establish that the axiomatic semantics of core GrE has a sound model in terms of its operational semantics.This establishes two key properties: equivalent terms ( ≡ ) are contextually equivalent in the operational semantics, and the graduality property holds.First, we review the de nition of the graduality property, and then we give a logical relations model and prove that any provable inequality ⊑ implies that the terms are related in the logical relation.

Static and Dynamic Gradual Guarantees
GrE is designed to support a smooth migration from imprecise to precise typing.The static gradual guarantee [Siek et al. 2015] formalizes a syntactic element of this idea of a smooth migration.The static gradual guarantee informally says that increasing the precision of type annotations on a program can only make it harder to satisfy the static type checker, or viewed the other way around, decreasing the precision of type annotations can only make it easier to satisfy the static type checker.Then the dynamic gradual guarantee, also known as graduality, establishes the semantic counterpart: increasing the precision of type annotations on a program should only make it harder to terminate without a dynamic type error, and furthermore except where there are dynamic type errors, the behavior of the program should match the original.These properties can be formalized as a form of monotonicity of the elaboration of the syntactic programs of surface GrE into the semantically meaningful core GrE programs as follows.First, we de ne a syntactic term precision ordering ⊑ syn on untyped GrE programs as the congruence closure of the type precision ordering.Then the static gradual guarantee says that this is a monotone partial function from the syntactic term precision ordering to the axiomatic inequality on core GrE terms: Then the dynamic gradual guarantee says that this extends to monotonicity in the following semantic ordering on core GrE terms: De nition 5.2 (Error Ordering on Closed Programs).Given • ⊢ ∅ , ′ : bool, de ne ⊑ sem ′ to hold when one of the following is satis ed (1) ↦ → * ℧, (2) ⇑ and ′ ⇑, (3) ↦ → * true and ′ ↦ → * true (4) ↦ → * false and ′ ↦ → * false.
This theorem is stated in terms of closed terms of a xed type, but to prove it we need a stronger inductive hypothesis, i.e., the logical relation for open terms.The resulting theorem that any inequality provable in the theory implies the semantic ordering is called graduality, as it is analogous in structure to the parametricity theorem in parametric polymorphism.Then the dynamic gradual guarantee follows as a corollary.

Logical Relation
We begin by introducing the notion of precision derivations in Figure 13, which will be used extensively in the de nition of the logical relation.A derivation : ⊑ ′ represents a proof that ⊑ ′ , and is built up inductively using the rules in the gure.Likewise, : ⊑ ′ is an inductively constructed proof witnessing the fact that is more precise than ′ .The bene t to making these derivations explicit in the syntax is that we can perform induction over them.As part of the de nition of e ect precision derivation, we use the notion of an e ect operation being "in" a precision derivation : ∈ .For when itself is a partial function this is just as with earlier usage, but when = ?or = inj( ′ ) we use the de nition at the bottom of the gure.
The assignment of derivations to type and e ect precision given in Figure 13 is equivalent to the de nition of precision given in Figure 5, in the sense that the choice does not a ect provability: Lemma 5.4 (Correctness of Precision Derivation Assignment).Assuming Σ ⊢ and Σ ⊢ , the following are equivalent • ⊑ ′ is provable in the system in Figure 5 • There exists a derivation Σ ⊢ : ⊑ ′ in the system in Figure 13.Similarly, assuming Σ ⊢ and Σ ⊢ ′ , the following are equivalent • ⊑ ′ is provable in the system in Figure 5 • There exists a derivation Σ ⊢ : ⊑ ′ in the system in Figure 13.
We also have that precision derivations are unique if they exist: Lemma 5.5 (Uniqeness of Precision Derivations).If ⊑ , then there is exactly one value type precision derivation such that : ⊑ .Likewise, if ⊑ ′ , then there is exactly one e ect type precision derivation such that : ⊑ ′ .
The de nition of the logical relation is given in Figure 15.Following prior work on logical relations for graduality, the relation is indexed not by types, but by type precision derivations.For a type precision derivation , de ne and to be the types such that : ⊑ , and analogously for e ect types.
Figure 14 de nes the notions of well typed value, term, and evaluation-context atoms.These are used in the de nition of the step-indexed logical relation for graduality.Given a value type precision derivation , the set VAtom consists of pairs of values ( 1 , 2 ) such that 1 has type and 2 has type .Similarly, given types and and an e ect type precision derivation , the set TAtom consists of pairs of terms ( 1 , 2 ) with value types and and e ect types and , respectively.An evaluation context can be thought of as a term with a hole, which when lled yields another term.For our purposes, an evaluation context corresponds to a continuation that accepts a value and returns a term.The type of the hole is the type of the input value to the At rst glance, it may seem as though we do not need to employ step-indexing in the logical relation.That is, it might seem that we could simply de ne the relation by induction on the structure of the derivation of ⊑ ′ .However, this would not su ce -there is indeed recursion in the logical relation, speci cally in the result relation R ∼ ⟦•⟧.This is discussed further below.
Given a step-indexed relation , we de ne an operator ▶ (pronounced "later ") as follows: Terms 1 and 2 are related in ▶ at index if and only if either is zero, or ≥ 1 and 1 and 2 are related in at index − 1.Many of the details of the logical relation are similar to prior work, especially [New et al. 2020], so we highlight the handling of e ect types, which is novel.In addition to the usual expression and value relations E ∼ ⟦•⟧ and V ∼ ⟦•⟧, we have a result relation R ∼ ⟦•⟧ and a continuation relation K ∼ ⟦•⟧.In our language, a result is either a value, or an evaluation context wrapping a raise of an operation , such that # .The result relation speci es the conditions for two such results to be related.
Each of the relations is parameterized by a precision derivation.In the case of the expression and result relations, this is an e ect precision derivation, while for values and continuations, it is a value type precision derivation.This is analogous to the usual approach whereby a logical relation is indexed by a type.But instead of using types, we use precision derivations, i.e., the proof that the type of the LHS term is more precise than the type of the RHS term.These derivations are used implicitly to constrain the types of the LHS and RHS terms.For instance, in the value relation for function types, the requirement that ( 1 , 2 ) ∈ VAtom → ensures not only that 1 and 2 have function type, but that the type of 1 is more precise than the type of 2 .
As in previous work on logical relations for graduality, the expression logical relation E ∼ ⟦•⟧ is split into two relations E ⪯ ⟦•⟧ and E ⪰ ⟦•⟧.The former counts the steps taken by the left-hand term, while the latter counts steps taken by the right-hand term.This is captured by the quantitative small-step reduction ↦ → which means takes exactly steps to reduce to .The other logical relations are also split into two versions in the same way.Despite needing two one-sided versions of each relation, we are for the most part able to abstract over their di erences: most of the lemmas we prove hold for both versions with no adjustment needed.Notable exceptions are transitivity and the anti-and forward reduction lemmas: these lemmas make crucial use of step counting, so naturally the side whose steps we are counting makes a di erence.
We note that in the de nition of the value relation for function types, V ∼ ⟦ ⟧ without the step-index should be interpreted as a partial application, i.e., it is a function from step indices to relations.
For the sake of clarity, we brie y outline the de nition of the two one-sided expression relations.In both relations, the rst clause is a "time-out" condition.In the case when we're counting steps on the left (i.e., E ⪯ ⟦•⟧), this states that if 1 takes + 1 or more steps, then it is automatically related at step index to 2 .An analogous rule holds when counting steps on the right: if 2 takes + 1 or more steps, then it is related to 1 at step index .The next clauses relate to errors.In the case of E ⪯ ⟦•⟧, if 1 errors in at most steps, then it is related to 2 regardless of the behavior of 2 .This models the axiom that error is the most precise term.In the case of E ⪰ ⟦•⟧, if 2 errors in at most steps, we ensure that 1 also errors (in any number of steps, since we're counting steps on the right).We also allow for the case where 2 reduces to a result in at most steps, and 1 errors.An equivalent way to phrase these rules that clari es the similarity between the two versions of the expression relation is that if 1 errors (in any number of steps), then it is related to 2 in E ⪰ ⟦•⟧ provided that 2 steps in at most steps to either an error, or a result.Finally, the last clauses concern the case when both 1 and 2 step to results, where as usual in E ⪯ ⟦•⟧ we require that 1 takes at most steps and in E ⪰ ⟦•⟧ we require that 2 takes at most steps.In both cases, we check that the results to which they step are related in the result relation at the appropriate step index.
One novel aspect of our logical relation is the result relation R ∼ ⟦ ⟧.The result relation relates terms 1 and 2 -of type and respectively -representing either two values or two "evaluations" of raised operations.The result relation is parameterized by a step-indexed relation between values of type and (the types of 1 and 2 ).(Ultimately, will end up being instantiated as V ∼ ⟦ ⟧ for some .) 1 and 2 are related by R ∼ ⟦ ⟧ at step index when either (1) both terms are values and are related by at index , or (2) there exists an e ect : in , values and related later, and evaluation contexts (i.e., continuations -see below) and related later, such that 1 is equal to raising the e ect and then wrapping it in the continuation, and likewise for 2 .Recall that is an e ect precision derivation; "membership" in such a derivation is de ned inductively on the structure of the derivation (the formal de nition is given in the appendix [New et al. 2023]).
Observe that the result relation is recursive: If is the dynamic e ect type ?then the de nitions of and may in general include ? in them.Thus, in order to maintain well-foundedness, when we refer to the value and continuation relations in this part of the de nition we need to "decrement the step index" (hence the use of the later operator).
The relation K ∼ ⟦•⟧ relates evaluation contexts 1 and 2 , similar to prior work on logical relations for continuations [Asai 2005].As mentioned above, evaluation contexts represent continuations that accept values.To enforce that the continuations accept values only, and not arbitrary terms, the inputs to the continuation relation are actually terms and with free variables and , respectively. 1 and 2 also have "output" types ( and ) and "output" e ect sets ( and ).When values are plugged into 1 and 2 , the result is two terms having types and and e ect sets and , respectively.

Proof of Graduality
Our goal is to prove that the inequational theory is sound with respect to the logical relation.First we de ne the notion of two terms being related semantically: That is, 1 and 2 are related if for all and all substitutions of values 1 and 2 related at , the resulting terms are related in E ∼ ⟦ ⟧V ∼ ⟦ ⟧, where this needs to hold both when ∼ is ⪯ and when it is ⪰.Our goal is then to prove the following: We provide here a high-level overview of the proof; the complete proofs are in the appendix [New et al. 2023].We begin by establishing variants of standard anti-and forward-reduction lemmas as well as monadic bind.We also prove a Löb induction principle to structure the induction over step-indices.With these lemmas, we rst prove soundness of each of the congruence rules for term precision, by uses of the monadic bind lemma along with the reduction lemmas.Next, we prove soundness of the rules of the equational theory, e.g., the and laws, and transitivity.Finally, we prove soundness of the rules for casts and subtyping.

DISCUSSION
Prior Work on Gradual E ects.The most signi cant prior work on gradual e ects is the work of Bañados Schwerter and collaborators [Bañados Schwerter et al. 2014], who de ned a gradual e ect system based on the generic e ect calculus of Marino and Millstein [2009] using an early version of the abstracting gradual typing (AGT) framework for gradual type systems [Garcia et al. 2016].While we based GrE on e ect handlers rather than the generic e ect calculus, there are signi cant similarities in the typing: function types and typing judgments are indexed by a set of e ect operations in each system.The most signi cant syntactic di erence is that their framework is parameterized by a xed e ect theory, whereas GrE has explicit support for declaration of new e ects in the program.In particular, this means that their system does not need to support modules containing di erent views of the same nominal e ect as we did.They additionally support a form of partially tracked functions, in GrE syntax this would look like → ,? , a function type where the function is known speci cally to possibly raise the e ect in addition to raising other e ects.In GrE this partial tracking would ensure that any e ects raised with the name match the module's local view of the e ect typing of .Finally, on the semantic side, this prior work proves only a type safety proof, whereas here we have proven graduality and the correctness of type-based optimizations and handler optimizations.
Another related area of research is on gradual typing with delimited continuations, which are mutually expressible with e ect handlers [Forster et al. 2019;Piróg et al. 2019].Takikawa and co-authors propose a gradual type system and semantics via contracts for a language with delimited continuations using typed prompts [Takikawa et al. 2013].They consider only value types and untracked function types that do not say which prompts are expected to be present.They show that a naive contract based implementation is unsound because a dynamically typed program can interact with a typed prompt and therefore the prompts themselves must be equipped with contracts, even though it does not correspond to any value being imported.In core GrE , this unsoundness is ruled out by using intrinsic typing: the problem corresponds to raising an e ect operation with a di erent type than the type expected by the closest handler, which is precisely what the e ect type system tracks.Wrapping the prompt in contracts is behaviorally equivalent to what is achieved by our e ect type casts.Sekiyama, Ueda and Igarashi present a blame calculus for a language with shift and reset [Sekiyama et al. 2015].The blame calculus is analogous to our core GrE language, and uses a type and e ect system for the answer types of shift/reset.They do not develop a surface language that elaborates to this blame calculus like our GrE , and there is no analog of e ect operations in shift/reset-based systems so there are no nominal aspects of their language.Additionally, while they have an e ect system to keep track of answer types, they do not have e ect casts.
Prior Approaches to Gradual Nominal Datatypes.We are also not the rst to consider the combination of gradual and nominal typing.The closest match to our design is in Typed Racket's support for typed structs.In Racket, a struct is a kind of record type that (by default) is generative in that it creates a new type tag distinct from all others.Typed Racket supports import of untyped Racket structs into Typed Racket, where types are assigned to the elds, and values of the struct type are then wrapped in contracts accordingly.This is quite close to our treatment of nominal e ect operations which can be thought of as adding new cases to the dynamic e ect monad rather than dynamic type.Our type system is more complex however, since in our system modules can use dynamically typed e ects whereas in Typed Racket, there is no syntactic type for dynamically typed values, when imported into typed code the system must give a completely precise type.Malewski and co-authors present a design for gradual typing with nominal algebraic datatypes [Malewski et al. 2021].Their focus is on the gradual migration from datatypes whose cases are open-ended to datatypes with a xed set of constructors.They do not consider the use-case we have where di erent modules have di erent typings for the same nominal constructor.
Prior Work on Subtyping.Much prior work on incorporating subtyping with gradual types has focused on the static typing aspects [Castagna et al. 2019;Garcia and Cimini 2015;Siek and Taha 2007;Wadler and Findler 2009] typing is the Abstracting Gradual Typing work [Garcia et al. 2016] which proves the dynamic gradual guarantee for a system with subtyping developed using the AGT methodology.In this work we establish equivalence between multiple di erent ways to combine gradual type casts and subtyping coercions, summarized in Figure 12, which are derivable from our newly identi ed cast/coercion ordering principle in our equational theory (Figure 10).
Towards a Practical Language Design.GrE is intended as a proof-of-concept language design to provide the semantic foundation for extending a language such as OCaml 5 with gradual e ect typing.We discuss the current mismatches with OCaml's design and how these might be recti ed.First, OCaml uses extensible variant types for e ects and exceptions, whereas in GrE e ects are not rst-class values.This should not be di cult to support as the variant type can be treated somewhat similarly to a dynamic type.Next, OCaml supports recursive e ect types, meaning that the request or response of an e ect can refer to the e ect being de ned.For instance, this allows for a variant of our coroutine example where forked threads can fork further threads.This would complicate the metatheory of GrE but should work in principle.The logical relation already supports a form of recursive e ect type in the form of the dynamic type, and so this could be extended to arbitrary recursive de nitions using step-indexing in a similar fashion.A nal syntactic di erence is that OCaml is based on Hindley-Milner-style polymorphic type schemes, whereas GrE is based on a simple type system.It may be possible to adapt previous work for gradual typing in uni cation-based type systems [Castagna et al. 2019;Garcia and Cimini 2015;Siek and Vachharajani 2008].
Implementing gradual e ects brings its own challenges.Our derivation of the operational semantics is based on proving that e ect casts can be implemented as handlers, and so can be implemented by a source-to-source transformation.However, such an implementation may su er from similar performance issues as other naive wrapper semantics, which can be solved by defunctionalizing the casts [Herman et al. 2010].Additionally, strong gradual typing between fully dynamically typed and static code can result in high performance penalties [Takikawa et al. 2016] even with space e cient implementations.However since e ect casts would not be as pervasive in typical programs as value type casts, it is not obvious that the same pathological behaviors would arise in gradually e ect typed OCaml programs.This is a clear empirical question to be addressed in future work.

Guarded Recursion as an alternative to Explicit
Step-Indexing.The later operator ▶ was originally studied by Nakano [Nakano 2000] as a modality for expressing guarded recursive types and this has been used along with the principle of Löb-induction ((▶ ⇒ ) ⇒ ) to develop domain-speci c logics for step-indexed logical relations [Dreyer et al. 2009].This allows for proofs to be carried out without explicit reference to step indices.More generally, the mathematical area of synthetic guarded domain theory (SGDT) has extended this approach from higher-order logic to a full modal dependent type theory [Bahr et al. 2017;Birkedal et al. 2011].Such an approach might considerably simplify the construction of a logical relations model by avoiding the explicit threading of steps, at the cost of using a non-standard meta-logic, and so would be an interesting avenue for future work.However, it is not clear how to adapt the nal graduality property from Section 5.3, which quanti es over all step indices to this setting.

Fig. 5 .
Fig. 5. Well formed types and e ects, Type and E ect Precision
imprecision of the e ect typing in this program means that programmers have to rely on documentation or understanding of the code to understand what e ects might be raised when Proc.ACM Program.Lang., Vol. 7, No. OOPSLA2, Article 284.Publication date: October 2023.