Compilation Semantics for a Programming Language with Versions

Programming with versions is a paradigm that allows a program to use multiple versions of a module so that the programmer can selectively use functions from both older and newer versions of a single module. Previous work formalized $\lambda_{\mathrm{VL}}$, a core calculus for programming with versions, but it has not been integrated into practical programming languages. In this paper, we propose VL, a Haskell-subset surface language for $\lambda_{\mathrm{VL}}$ along with its compilation method. We formally describe the core part of the VL compiler, which translates from the surface language to the core language by leveraging Girard's translation, soundly infers the consistent version of expressions along with their types, and generates a multi-version interface by bundling specific-version interfaces. We conduct a case study to show how VL supports practical software evolution scenarios and discuss the method's scalability.


Introduction
Updating dependent software packages is one of the major issues in software development. Even though a newer version of a package brings improvements, it also brings the risk of breaking changes, which can make the entire software defective.
We argue that this issue originates from the principle of most programming languages that only allow the use of one version of a package at a time. Due to this principle, developers are faced with the decision to either update to a new, improved version of a package that requires many changes or to remain with an older version. The problem gets worse when a package is indirectly used. This dilemma often results in delays in adopting upgrades, leading to stagnation in software development and maintenance [16,2]. Programming with versions [28,29,15,30] is a recent proposal that allows programming languages to support multiple versions of programming elements at a time so that the developer can flexibly cope with incompatible changes. λ VL is the core calculus in which a versioned value encapsulates multiple versions of a value (including a function value). The λ VL type system checks the consistency of each term so that a value produced in a version is always passed to functions in the same version. The calculus and the type system design are based on coeffect calculus [3,20].
While λ VL offers the essential language constructs to support multiple versions in a program, the language is far from practical. For example, with multiple versions of a module, each version of the function must be manually represented inside a versioned value (i.e., a record-like expression). λ VL is as simple as lambda calculus, yet it has a verbose syntax due to the coeffect calculus. In short, there are aspects of versioning in λ VL that a surface language compiler can automate.
We propose the functional language VL as a surface language for λ VL along with its compilation method. In VL, a function name imported from an external module represents a multi-version term, where each occurrence of the function name can reference a different version of the function. The VL compiler translates a program into an intermediate language VLMini, a version-label-free variant of λ VL , determines the version for each name occurrence based on a type and version inference algorithm, and translates it back into a version-specialized Haskell program. VL also offers the constructs to explicitly control versions of expressions, which are useful to keep using an older version for some reason.
This paper presents the following techniques in VL: (a) an application of Girard's translation for translating VL into VLMini, (b) the bundling for making a top-level function act as a versioned value, and (c) a type and version inference algorithm for identifying the version of each expression with respect to the λ VL type system. Finally, we prove the soundness of the inference system and implement a VL compiler. Code generation converts a VL program into a version-specialized Haskell program using the solution obtained from z3 [18].
Paper Organization. Section 2 introduces incompatibility issues and fundamental concepts in programming with versions with λ VL and VL. Section 3 introduces bundling and Girard's transformation. Section 4 presents an algorithmic version inference for VL. Section 5 features an implementation of VL, and Section 6 introduces a case study that simulates an incompatible update made in a Haskell library. Finally, Section 7 discusses further language development and concludes the paper by presenting related work and a conclusion.

Motivating Example
First, we will explain a small example to clarify incompatibility issues. Consider a scenario where an incompatible change is made to a dependent package.   Although the update does not modify App, it causes errors within App. Even if a file with an input filename exists, the program returns Not Found error contrary to the expected behavior. The cause of the unexpected output lies in the differences between the two versions required for main. In line 6 of App, an SHA-3 hash value is generated by mkHash and assigned to digest. Since exists evaluates hash equivalence using MD5, exists digest compares hashes generated by different algorithms, evaluating to false.
This example highlights the importance of version compatibility when dealing with functions provided by external packages. Using different versions of Hash in separate program parts is fine, but comparing results may be semantically incorrect. Even more subtle changes than those shown in Figure 1 can lead to significant errors, especially when introducing side effects or algorithm modifications that break the application's implicit assumptions. Manually managing version compatibility for all external functions is unfeasible.
In practical programming languages, dependency analysis is performed before the build process to prevent such errors, and package configurations requiring multiple versions of the same package are rejected. However, this approach tends towards conservative error reporting. In cases where a core package, which many other libraries depend on, receives an incompatible change, no matter how minuscule, it requires coordinated updates of diverse packages across the entire package ecosystem [2,29,31].

λ VL
λ VL [28,29] is a core calculus designed to follow the principles: (1) enabling simultaneous usage of multiple versions of a package, (2) ensuring version consistency within a program. λ VL works by encapsulating relevant terms across multiple versions into a record-like term, tagged with a label indicating the specific module version. Record-like terms accessible to any of its several versions are referred to as versioned values, and the associated labels are called version labels. Figure 2 shows the syntax of λ VL . Given modules and their versions, the corresponding set of version labels characterizes the variation of programs of a versioned value. In λ VL , version labels are implicitly generated for all external module-version combinations, in which M i is unique, with the universal set of these labels denoted by L. Specifically, in the example illustared in Figure  Syntax of λ VL λ VL extends ℓRPCF [3] and GrMini [20] with additional terms that facilitate introducing and eliminating versioned values. Versioned values can be introduced through versioned records {l i = t i } and promotions [t]. A versioned record encapsulates related definitions t 1 , . . . , t n across multiple versions  and their version labels l 1 , . . . , l n . For instance, the two versions of mkHash in Figure 1 can be bundled as the following version record.

Version Labels
In λ VL , programs are constructed via function application of versioned values. A function application of mkHash to the string s can be written as follows.
This program (app hereafter) makes a hash for the string "compiler.vl" and is available for both l 1 and l 2 . The contextual let-binding let [x] = t 1 in t 2 provides the elimination of version values by binding a versioned value for t 1 to x, thus making it accessible in t 2 . Promotion [x] offers an alternative way to introduce versioned values, making any term t act as a versioned value.
The evaluation of terms t i stored in a versioned value {l i = t i } and [t] is postponed until a specific version label is later specified. To proceed with a postponed evaluation of a versioned value, we use extraction u.l k . Extraction specifies one versioned label l k for the versioned value u and recursively extracts the inner term t k corresponding to l k from {l i = t i }, and t from [t] as follows.
Consequently, app#l 1 evaluates into an MD5 hash corresponding to l 1 .

Type of Versioned Values
The type of a versioned value is expressed as P r A, assigning a set of version labels r, called version resources, to a type A. Intuitively, the type of a versioned value represents the versions available to that versioned value. For example, mkHash and app are typed as follows.  The types have {l 1 , l 2 } as their version resource, illustrating that the versioned values have definitions of l 1 and l 2 . For function application, the type system computes the intersection of the version resource of subterms. Since the promoted term is considered to be available in all versions, the version resource of the entire function application indicates {l 1 , l 2 } = {l 1 , l 2 } ∩ L.
For extractions, the type system verifies if the version resource contains the specified version as follows. app#l 1 : String → String app#l 3 : (rejected) Assuming L = {l 1 , l 2 , l 3 }, app#l 3 is rejected by type checking because the version resource of app does not contain l 3 . Conversely, app#l 1 is well-typed, but note that the resultant type lost its version resource. It is attributed to the design principle that it could be used in other versions upon extraction.
The λ VL type system incorporates the notion of version consistency in addition to the standard notions of preservation and progress. Proofs of these theorems can be found in Appendix C.

Programming with Versions in VL
Our contributions enjoy the benefits of programming with versions on a λcalculus-based functional language VL. To achieve this, we develop a compilation method between lambda calculus and VLMini, a version-label free variant of λ VL , and a version inference algorithm to infer the appropriate version of expressions.
In VL, (1) all versions are available for every module, and (2) the version of each expression is determined by expression-level dependency analysis. This approach differs from existing languages that determine one version for each dependent package. Figure 3 shows how the programs in Figure 1 are interpreted in VL. The VL compiler bundles the interfaces of multiple versions and generates a cross-version interface to make external functions available in multiple versions. The VL type system enforces version consistency in main and selects a newer version if multiple versions are available. Thus it gives the version label {Hash = 2.0.0, Dir = 1.0.0} to dependent expressions of main. As a result, since Hash version referenced from Dir is no longer limited to 1.0.0, exists digest is evaluated using SHA-3 under the context of Hash version 2.0.0.
Furthermore, VL provides version control terms to convey the programmer's intentions of versions to the compiler. For example, to enforce the evaluation in Figure 3 to MD5, a programmer can rewrite line 7 of App as follows. The program dictates that exists digest is evaluated within the context of the Hash version 1.0.0. Consequently, both mkHash and match, which depend on exists digest, are chosen to align with version 1.0.0 of Hash. Moreover, VL provides unversion t. It eliminates the dependencies associated with term t, facilitating its collaboration with other versions under the programmer's responsibility, all while maintaining version consistency within its subterm. Thus, VL not only ensures version consistency but also offers the flexibility to control the version of a particular part of the program.

Compilation
The entire translation consists of three parts: (1) Girard's translation, (2) an algorithmic type inference, and (3) bundling. Figure 4 shows the translation process of a single module. First, through Girard's translation, each version of the VL program undergoes a version-wise translation into the VLMini program. Second, the type inference synthesizes types and constraints for top-level symbols. Variables imported from external modules reference the bundled interface generated in the subsequent step. Finally, to make the external variables act as multi-version expressions, bundling consolidates each version's interface into one VLMini interface. These translations are carried out in order from downstream of the dependency tree. By resolving all constraints up to the main module, the appropriate version for every external variable is determined.
It is essential to note that the translations focus on generating constraints for dispatching external variables into version-specific code. While implementing versioned records in λ VL presents challenges, such as handling many version labels and their code clones, our method is a constraint-based approach in VLMini that enables static inference of version labels without their explicit declaration.
In the context of coeffect languages, constraint generation in VL can be seen as the automatic generation of type declarations paired with resource constraints. Granule [20] can handle various resources as coeffects, but it requires type declarations to indicate resource constraints. VL restricts its resources solely to the version label set. This specialization enables the automatic collection of version information from external sources outside the codebase.

An Intermediate Language, VLMini
Syntax of VLMini Figure 5 shows the syntax of VLMini. VLMini encompasses all the terms in λ VL except for versioned records {l i = t i }, intermediate term ⟨l i = t i | l k ⟩, and extractions t.l k . As a result, its terms are analogous to those in ℓRPCF [3] and GrMini [20]. However, VLMini is specialized to treat version resources as coeffects. We also introduce data constructors by introduction C t 1 , ..., t n and elimination case t of p i → t i for lists and pairs, and version control terms unversion t and version {M i = V i } of t. Here, contextual-let in λ VL is a syntax sugar of lambda abstraction applied to a promoted pattern.
Types, version labels, and version resources are almost the same as λ VL . Type constructors are also added to the type in response to the VLMini term having a data constructor. The remaining difference from λ VL is type variables α. Since VLMini is a monomorphic language, type variables act as unification variables; type variables are introduced during the type inference and are expected to be either concrete types or a set of version labels as a result of constraint resolution. To distinguish those two kinds of type variables, we introduce kinds κ. The kind Labels is given to type variables that can take a set of labels {l i } and is used to distinguish them from those of kind Type during algorithmic type inference.
Constraints The lower part of Figure 5 shows constraints generated through bundling and type inference. Dependency constraints comprise variable dependencies and label dependencies in addition to propositional formulae. Variable dependencies α ⊑ α ′ require that if a version label for α ′ expects a specific version for a module, then α also expects the same version. Similarly, label dependencies α ⪯ ⟨⟨M i = V i ⟩⟩ require that a version label expected for α must be V i for M i . If the constraint resolution is successful, α will be specialized with either of two labels. Θ is a set of type equations resolved by the type unification.
The translation replaces lambda abstractions and function applications of VL by lambda abstraction with promoted pattern and promotion of VLMini, respectively. From the aspect of types, this translation replaces all occurrences of A → B with P r A → B with a version resource r. This translation inserts a syntactic annotation [ * ] at each location where a version resource needs to be addressed. Subsequent type inference will compute the resource at the specified location and produce constraints to ensure version consistency at that point.
The original Girard's translation [11] is well-known as a translation between the simply-typed λ-calculus and an intuitionistic linear calculus. The approach involves replacing every intuitionistic arrow A → B with !A ⊸ B, and subsequently unboxing via let-in abstraction and promoting during application [20].

Bundling
Bundling produces an interface encompassing types and versions from every module version, allowing top-level symbols to act as multi-version expressions. During this process, bundling reviews interfaces from across module versions, identifies symbols with the same names and types after removing □ r using Girard's transformation, and treats them as multiple versions of a singular symbol (also discussed in Section 7). In a constraint-based approach, bundling integrates label dependencies derived from module versions, ensuring they align with the version information in the typing rule for versioned records of λ VL .
For example, assuming that the id that takes an Int value as an argument is available in version 1.0.0 and 2.0.0 of M as follows: where α 1 and α 2 are version resource variables given from type inference. They capture the version resources of id and its argument value in version 1.0.0. C 1 is the constraints that resource variables of version 1.0.0 will satisfy. Likewise for β 1 , β 2 , and C 2 . Since the types of id in both versions become Int → Int via Girard's translation, they can be bundled as follows: where γ 1 and γ 2 are introduced by this conversion for the bundled id interface, with label and variable dependencies that they will satisfy. γ 1 captures the version resource of the bundled id . The generated label dependencies γ 1 ⪯ ⟨⟨M = 1.0.0⟩⟩ and γ 1 ⪯ ⟨⟨M = 2.0.0⟩⟩ indicate that id is available in either version 1.0.0 or 2.0.0 of M. These label dependencies are exclusively 4 generated during bundling. The other new variable dependencies indicate that the id bundled interface depends on one of the two version interfaces. The dependency is made apparent by pairing the new resource variables with their respective version resource variable for each version. These constraints are retained globally, and the type definition of the bundled interface is used for type-checking modules importing id .

Algorithmic Type Inference
We develop the algorithmic type inference for VLMini derived from the declarative type system of λ VL [28,29]. The type inference consists of two judgments: type synthesis and pattern type synthesis. The judgment forms are similar to Gr [20], which is similarly based on coeffect calculus. While Gr provides typechecking rules in a bidirectional approach [8,9] to describe resource constraint annotations and performs unifications inside the type inference, VLMini only provides synthesis rules and unification performs after the type inference. In addition, Gr supports user-defined data types and multiple computational resources, while VLMini supports only built-in data structures and specializes in version resources. The inference system is developed to be sound for declarative typing in λ VL , with the proof detailed in Appendix D. Type synthesis takes type variable kinds Σ, a typing context Γ of term variables, and a term t as inputs. Type variable kinds Σ are added to account for distinct unification variables for types and version resources. The synthesis produces as outputs a type A, type variable kinds Σ ′ , type constraints Θ, and dependency constraints C. The type variable kinds Σ and Σ ′ always satisfy Σ ⊆ Σ ′ due to the additional type variables added in this phase.
Pattern type synthesis takes a pattern p, type variable kinds Σ, and resource environment R as inputs. It synthesizes outputs, including typing context Γ , type variable kinds Σ ′ , and type and dependency constraints Θ and C. Pattern type synthesis appears in the inference rules for λ-abstractions and case expressions. It generates a typing context from the input pattern p for typing λ-bodies and branch expressions in case statements. When checking a nested promoted pattern, the resource context R captures version resources inside a pattern.

Pattern Type Synthesis
Pattern type synthesis conveys the version resources captured by promoted patterns to the output typing context. The rules are classified into two categories, whether or not it has resources in the input resource context R. The base rules are pVar, pP, while the other rules are resource-aware versions of the corresponding rules. The resource-aware rules assume they are triggered within the promoted pattern and collect version resource r in the resource context.
The rules for variables pVar and [pVar] differ in whether the variable pattern occurs within a promoted pattern. pVar has no resources in the resource context because the original pattern is not inside a promoted pattern. Therefore, this pattern produces typing context x : A.
[pVar] is for a variable pattern within the promoted pattern, and a resource r is recorded in the resource context. The rule assigns the collected resource r to the type A and outputs it as a versioned assumption x : The rules for promoted patterns p□ propagate version resources to the subpattern synthesis. The input type A is expected to be a versioned type, so the rule generates the fresh type variables α and β, then performs the subpattern synthesis considering A as P α β. Here, the resource α captured by the promoted pattern is recorded in the resource context. Finally, the rule unifies A and P α β and produces the type constraints Θ ′ for type refinement.

Type Synthesis
The algorithmic typing rules for VLMini, derived from declarative typing rules for λ VL , are listed in Figure 6. We explain a few important rules in excerpts.
The rule ⇒ abs generates a type variable α, along with the binding pattern p of the λ-abstraction generating the typing context Γ ′ . Then the rule synthesizes a type B for the λ-body under Γ ′ , and the resulting type of the λ-abstraction is α → B with the tentatively generated α. With the syntax sugar, the type rules of the contextual-let are integrated into ⇒ abs . Instead, λ-abstraction does not just bind a single variable but is generalized to pattern matching, which leverages pattern typing, as extended by promoted patterns and data constructors.
The rule ⇒ pr is the only rule that introduces constraints in the entire type inference algorithm. This rule intuitively infers consistent version resources for the typing context Γ . Since we implicitly allow for weakening, we generate a constraint from Γ ′ that contains only the free variables in t, produced by context grading denoted as [Γ ] Labels . Context grading converts all assumptions in the input environment into versioned assumptions by assigning the empty set for the assumption with no version resource.
Finally, the rule generates constraints from Γ ′ and a fresh type variable α by constraints generation defined in the lower part of Figure 6. The rules assert that the input type variable α is a subset of all the resources of the versioned assumptions in the input environment Γ . The following judgment is the simplest example triggered by the type synthesis of [f x].
r : Labels, s : The inputs are type variable α and the type environment (f : [Int → Int] r , x : [Int] s ). In this case, the rules generate variable dependencies for r and s, each resource of the assumptions, and return a constraint combined with ∧.

Extensions
Version Control Terms The rule for version l of t uses the same trick as (⇒ pr ), and generates label dependencies from the input environment Γ to ⟨⟨l⟩⟩. Since version l of t only instructs the type inference system, the resulting type is the same as t. unversion t removes the version resource from the type of t, which is assumed to be a versioned value. We extend Girard's translation so that t is always a versioned value. Since a new resource variable is given to the term by the promotion outside of unversion, the inference system guarantees the version consistency inside and outside the boundary of unversion. The list of the rules is provided in Appendix B.4.

Data Structures
To support data structures, Hughes et al. suggest that coeffectful data types are required to consider the interaction between the resources inside and outside the constructor [13]. They introduce the derivation algorithm for push and pull for an arbitrary type constructor K to address this. Following their approach, we developed inference rules for pairs and lists. When a data structure value p is applied to a function f , the function application f p is implicitly interpreted as f (pull p). As a dual, a pattern match for a data structure value case p of p i → t i is interpreted as case (push p) of p i → t i . Appendix B.5 provides the complete set of extended rules.

Implementation
We implement the VL compiler 5 on GHC (v9.2.4) with haskell-src-exts 6 as its parser with an extension of versioned control terms, and z3 [18] as its constraint version join vjoin udot, sortVector, roundVector < 0.15 available undefined undefined ≥ 0.16 deleted available available Table 1. Availability of functions in hmatrix before and after tha update.
solver. The VL compiler performs the code generation by compiling VLMini programs back into λ-calculus via Girard's translation and then translating them into Haskell ASTs using the version in the result version labels.

Ad-hoc Version Polymorphism via Duplication
The VL compiler replicates external variables to assign individual versions to homonymous external variables. Duplication is performed before type checking of individual versions and renames every external variable along with the type and constraint environments generated from the import declarations. Such ad hoc conversions are necessary because VLMini is monomorphic, and the type inference of VLMini generates constraints by referring only to the variable's name in the type environment. Therefore, assigning different versions to homonymous variables requires manual renaming in the preliminary step of the type inference. A further discussion on version polymorphism can be found in Section 7.
Constraints Solving with z3 We use sbv 7 as the binding of z3. The sbv library internally converts constraints into SMT-LIB2 scripts [1] and supplies it to z3. Dependency constraints are represented as vectors of symbolic integers, where the length of the vector equals the number of external modules, and the elements are unique integers signifying each module's version number. Constraint resolution identifies the expected vectors for symbolic variables, corresponding to the label on which external identifiers in VL should depend. If more than one label satisfies the constraints, the default action is to select a newer one.

Case Study
We demonstrate that VL programming achieves the two benefits of programming with versions. The case study simulated the incompatibility of hmatrix, 8 a popular Haskell library for numeric linear algebra and matrix computations, in the VL module Matrix. This simulation involved updating the applications Main depending on Matrix to reflect incompatible changes. Table 1 shows the changes introduced in version 0.16 of hmatrix. Before version 0.15, hmatrix provided a join function for concatenating multiple vectors. The update from version 0.15 to 0.16 replaced join with vjoin. Moreover, several new functions were introduced. We implement two versions of Matrix to simulate backward incompatible changes in VL. Also, due to the absence of user-defined types in VL, we represent Vector a and Matrix a as List Int and List (List Int) respectively, using List, a partial port of Data.List from the Haskell standard library.
We sortVector simultaneously is acceptable, as their return values are vectors and matrices. Therefore, we apply unversion t for t to collaborate with other versions. The right side of Figure 7 shows a rewritten snippet of Main, where sortVector vec is replaced by unversion (sortVector vec). Assuming we avoid using programs that depend on a specific version elsewhere in the program, we can successfully compile and execute main.

Scalability of Constraint Resolution
We conducted experiments on the constraint resolution time of the VL compiler.
In the experiment, we duplicated a VL module, renaming it to #mod like List i, and imported each module sequentially. Every module had the same number of versions, denoted as #ver. Each module version was implemented identically to List, with top-level symbols distinguished by the module name, such as concat List i. The experiments were performed ten times on a Ryzen 9 7950X running Ubuntu 22.04, with #mod and #ver ranging from 1 to 5. Figure 8 shows the average constraint resolution time. The data suggests that the resolution time increases polynomially (at least square) for both #mod and #ver. Several issues in the current implementation contribute to this inefficiency: First, we employ sbv as a z3 interface, generating numerous redundant variables in the SMT-Lib2 script. For instance, in a code comprising 2600 LOC (with #mod = 5 and #ver = 5), the VL compiler produces 6090 version resource variables and the sbv library creates SMT-Lib2 scripts with approximately 210,000 intermediate symbolic variables. Second, z3 solves versions for all AST nodes, whereas the compiler's main focus should be on external variables and the subterms of unversion. Third, the current VL nests the constraint network, combined with ∨, #mod times at each bundling. This approach results in an overly complex constraint network for standard programs. Hence, to accelerate constraint solving, we can develop a more efficient constraint compiler for SMT-Lib2 scripts, implement preprocess to reduce constraints, and employ a greedy constraint resolution for each module.

Related Work, Future Work, and Conclusion
Managing Dependency Hell Mainstream techniques for addressing dependency hell stand in stark contrast to our approach, which seeks to manage dependencies at a finer granularity. Container [17] encapsulates each application with all its dependencies in an isolated environment, a container, facilitating multiple library versions to coexist on one physical machine. However, it does not handle internal dependencies within the container. Monorepository [21,10] versions logically distinct libraries within a single repository, allowing updates across multiple libraries with one commit. It eases testing and bug finding but can lower the system modularity.
Toward a Language Considering Compatibility The next step in this research is to embed compatibility tracking within the language system. The current VL considers different version labels incompatible unless a programmer uses unversion. Since many updates maintain backward compatibility and change only minor parts of the previous version, the existing type system is overly restrictive.
To illustrate, consider Figure 3 again with more version history. The module Hash uses the MD5 algorithm for mkHash and match in the 1.
x.x series. However, it adopts the SHA-3 algorithm in version 2.0.0, leaving other functions the same. The hash by mkHash version 1.0.1 (an MD5 hash) aligns with any MD5 hash from the 1.
x.x series. Therefore, we know that comparing the hash using match version 1.0.0 is appropriate. However, the current VL compiler lacks mechanisms to express such compatibility in constraint resolution. The workaround involves using unversion, risking an MD5 hash's use with match version 2.0.0.
One promising approach to convey compatibilities is integrating semantic versioning [22] into the type system. If we introduce semantics into version labels, the hash generated in version 1.0.1 is backward compatible with version 1.0.0. Thus, by constructing a type system that respects explicitly defined version compatibilities, we can improve VL to accept a broader range of programs.
It is important to get reliable versions to achieve this goal. Lam et al. [14] emphasize the need for tool support to manage package modifications and the importance of analyzing compatibility through program analysis. Delta-oriented programming [26,25,24] could complement this approach by facilitating the way modularizing addition, overriding, and removal of programming elements and include application conditions for those modifications. This could result in a sophisticated package system that provides granular compatibility information.
Such a language could be an alternative to existing technologies for automatic update, collectively known as adoptation. These methods generate replacement rules based on structural similarities [5,32] and extract API replacement patterns from migrated code bases [27]. Some techniques involve library maintainers recording refactorings [7,12] and providing annotations [4] to describe how to update client code. However, the reported success rate of these techniques is less than 20% on average [6].
Supporting Type Incompatibility One of the apparent problems with the current VL does not support type incompatibilities. VL forces terms of different versions to have the same type, both on the theoretical (typing rules in λ VL ) and implementation (bundling in VLMini) aspects. Supporting type incompatibility is important because type incompatibility is one of the top reasons for error-causing incompatibilities [23]. The current VL is designed in such a way because it retains the principle that equates the types of promotions and versioned records in λ VL , easing the formalization of the semantics.
A promising approach to address this could be to decouple version inference from type inference and develop a version inference system on the polymorphic record calculus [19]. The idea stems from the fact that versioned types P {l1,l2} A are structurally similar to record types {l 1 : A, l 2 : A} of Λ ∀,• . Since Λ ∀,• allows different record-element types for different labels and has concrete inference algorithms with polymorphism, implementing version inference on top of Λ ∀,• would also make VL more expressive.
Adequate Version Polymorphism In the current VL, there is an issue that the version label of top-level symbols in imported modules must be specified one, whereas users can select specific versions of external variables using unversion within the importing module. Consider using a generic function like List.concat in Figure  It is necessary to introduce full-version polymorphism in the core calculus instead of duplication to address this problem. The idea is to generate a type scheme by solving constraints for each module during bundling and instantiate each type and resource variable at each occurrence of an external variable. Such resource polymorphism is similar to that already implemented in Gr [20]. However, unlike Gr, VLMini provides a type inference algorithm that collects constraints on a per-module basis, so we need the well-defined form of the principal type. This extension is future work.
Conclusion This paper proposes a method for dependency analysis and version control at the expression level by incorporating versions into language semantics, which were previously only identifiers of packages. This enables the simultaneous use of multiple versions and identifies programs violating version consistency at the expression level, which is impossible with conventional languages. Our next step is to extend the version label, which currently only identifies versions, to semantic versions and to treat the notion of compatibility with language semantics. Like automatic updates by modern build tools based on semantic versioning, it would be possible to achieve incremental updates, which would be done step-by-step at the expression level. Working with existing package managers to collect compatibility information at the expression level would be more feasible to realize the goal.
where we use the notations ⊢ Rw and ⊢ Rew in (Rew r ) to represent the judgements of resource and resource environment well-formedness respectively, to avoid ambiguity between two syntactically indistinguishable judgements.
Default version overwriting VLMini constraints VLMini type substitutions

VLMini constraints generation
otherwise fail.

B.4 Extensions for Version Control Terms
VLMini syntax t ::= . . . | version l of t | unversion t (terms) VLMini algorithmic type synthesis Note that type environment resources in VLMini always contain only type variables, so r = α (∃α).
where ⊥ is the smallest element of R, and r 1 ⊆ r 2 is the standard subset relation over sets defined only when both r 1 and r 2 are not ⊥.
Definition 2 (Version resource summation). Using the addition + of version resource semiring, summation of version resouce is defined as follows:

C.2 Context Properties
Definition 3 (Context concatenation). Two typing contexts can be concatenated by "," if they contain disjoint assumptions. Furthermore, the versioned assumptions appearing in both typing contexts can be combined using the context concatenation + defined with the addition ⊕ in the version resource semiring as follows.

Definition 4 (Context multiplication by version resource).
Assuming that a context contains only version assumptions, denoted [Γ ] in typing rules, then Γ can be multiplied by a version resource r ∈ R by using the product ⊗ in the version resource semiring, as follows.
Γ 1|Γ 2 is a subsequence of Γ 1 that contains all the term variables that are included in Γ 2 , and Γ 1 |Γ2 is a subsequence of Γ 1 that contains all the term variables that are not included in Γ 2 .
Using Γ 1|Γ 2 and Γ 1 |Γ2 , we state some corollaries about typing contexts. These theorems follow straightforwardly from the definitions of 6.

Lemma 5 (Distribution of version resouce over context addition).
For a typing context Γ and resources r i ∈ R: Lemma 6 (Disjoint context collapse). Given typing contexts Γ 1 , ∆, and Γ 2 such that Γ 1 and Γ 2 are disjoint, then we can conclude the following.
Proof. This proof is given by induction on the structure of Γ, x : A, Γ ′ ⊢ t : B (assumption 2). Consider the cases for the last rule used in the typing derivation of assumption 2.
-Case (int) In this case, the above typing context is empty (= ∅), so this case holds trivially.
-Case (var) We are given

Now the conclusion of the lemma is
Since [t ′ /y]y = t ′ from the definition of substitution, the conclusion of the lemma is assumption 1 itself.
-Case (let) This case is similar to the case (app).
-Case (app) We are given By the definition of the context addition +, the linear assumption x : A is contained in only one of Γ 1 or Γ 2 .
Now, we compare the typing contexts between the lemma and the above conclusion as follows: By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we know Γ = (Γ ′ 1 + Γ 2|Γ ′ 1 ) and ). We then apply the induction hypothesis to each of the two premises and reapply (app) as follows: , the conclusion of the above derivation is equivalent to the conclusion of the lemma except for the typing contexts. Finally, we must show that (Γ + ∆ + Γ ′ ) = ((Γ 1 + ∆ + Γ ′′ 1 ) + Γ 2 ). This holds from the following reasoning: Thus, we obtain the conclusion of the lemma. • Suppose (x : A) / ∈ Γ 1 and (x : A) ∈ Γ 2 Let Γ ′ 2 and Γ ′′ 2 be typing contexts such that they satisfy Γ 2 = (Γ ′ 2 , x : A, Γ ′′ 2 ). The last typing derivation of (app) is rewritten as follows.
-Case (weak) In this case, the linear assumption x : A is not contained in versioned context [∆ ′ ] 0 . We then compare the typing contexts between the conclusion of the lemma and that of (weak) as follows: By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we obtain We then apply the induction hypothesis to each of the premise and reapply (weak) as follows: , the conclusion of the above derivation is equivalent to the conclusion of the lemma except for typing contexts.
-Case (der) Γ, x : A, Γ ′′ , y : In this case, a linear assumption x : A cannot be a versioned assumption y : [B 1 ] 1 . Applying the induction hypothesis to the premise, we obtain the following: Note that Γ + ∆ + (Γ ′′ , y : B 1 ) = (Γ + ∆ + Γ ′′ ), y : B 1 holds because y : B 1 is a linear assumption and is disjoint with Γ , ∆, and Γ ′′ . Thus, the above judgement is equivalent to the following: We then reapply (der) to obtain the following: Finally, since y : Thus, the conclusion of the above derivation is equivalent to the following: Thus, we obtain the conclusion of the lemma.
-Case (pr) This case holds trivially, because the typing context [Γ ] contains only versioned assumptions and does not contain any linear assumptions.
-Case (ver) This case holds trivially, because the typing context of the conclusion contains only versioned assumptions (by [Γ i ] in the premise) and does not contain any linear assumptions.
-Case (veri) This case holds trivially, because the typing context of the conclusion contains only versioned assumptions (by [Γ i ] in the premise) and does not contain any linear assumptions.
-Case (extr) In this case, we apply the induction hypothesis to the premise and then reapply (extr), we obtain the conclusion of the lemma.
-Case (sub) In this case, a linear assumption x : A cannot be a versioned assumption y : [B 1 ] s , and only one of (x : A) ∈ Γ or (x : A) ∈ Γ ′ holds. In either case, applying the induction hypothesis to the premise and reappling (sub), we obtain the conclusion of the lemma.

Lemma 8 (Well-typed versioned substitution).
[∆] ⊢ t ′ : A Proof. This proof is given by induction on structure of Γ, x : [A] r , Γ ′ ⊢ t : B (assumption 2). Consider the cases for the last rule used in the typing derivation of assumption 2.
-Case (int) This case holds trivially because the typing context of (int) is empty (= ∅).
-Case (var) In this case, x : [A] r is a versioned assumption and y : B is a linear assumption, so x ̸ = y holds, and yet the typing context besides y : B is empty. Thus, there are no versioned variables to be substituted, so this case holds trivially.
-Case (abs) In this case, we know the following by applying induction hypothesis to the partial derivation of (abs): is disjoint with Γ , ∆, and Γ ′ . Thus, Γ + r · ∆ + (Γ ′ , ∆ ′ ) = (Γ +r ·∆+Γ ′ ), ∆ ′ from Lemma 3 (2), the typing derivation above is equal to the following: We then reapply (abs) to obtain the following: from the definition of substitution, we obtain the conclusion of the lemma.
-Case (app) We are given By the definition of the context addition +, the linear assumption x : A is contained in either or both of the typing context Γ 1 or Γ 2 .
• Suppose (x : [A] r ) ∈ Γ 1 and x / ∈ dom(Γ 2 ) Let Γ ′ 1 and Γ ′′ 1 be typing contexts such that they satisfy Γ 1 = (Γ ′ 1 , x : [A] r , Γ ′′ 1 ). The last derivation of (app) is rewritten as follows: We compare the typing contexts between the conclusion of the lemma and that of the above derivation to obtain the following: By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we know Γ = (Γ ′ 1 + Γ 2|Γ ′ 1 ) and ). We then apply the induction hypothesis to each of the two premises of the last derivation and reapply (app) as follows: , the conclusion of the above derivation is equivalent to the conclusion of the lemma except for the typing contexts. Finally, we must show that (Γ + r · ∆ + Γ ′ ) = ((Γ 1 + r · ∆ + Γ ′′ 1 ) + Γ 2 ). This holds from the following reasoning: Thus, we obtain the conclusion of the lemma. • Suppose x / ∈ dom(Γ 1 ) and (x : [A] r ) ∈ Γ 2 Let Γ ′ 2 and Γ ′′ 2 be typing contexts such that they satisfy Γ 2 = (Γ ′ 2 , x : . The last typing derivation of (app) is rewritten as follows: This case is similar to the case (x : [A] r ) ∈ Γ 1 and x / ∈ dom(Γ 2 ), but using 3 (2) instead of 3 (1).
Let Γ ′ 1 , Γ ′′ 1 , Γ ′ 2 , and Γ ′′ 2 be typing contexts such that they satisfy . The last derivation of (app) is rewritten as follows: Now, we compare the typing contexts between the lemma and the above conclusion as follows: (∵ , associativity) By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we know Γ = (Γ ′ 1 +Γ ′ ). We then apply the induction hypothesis to each of the two premises of the last derivation and reapply (app) as follows: , the conclusion of the above derivation is equivalent to the conclusion of the lemma except for the typing contexts. Finally, we must show that 2 ) (∵ + associativity and commutativity) Thus, we obtain the conclusion of the lemma.
-Case (weak) In this case, we know (Γ, x : There are two cases where the versioned assumption x : [A] r is contained in [∆ ′ ] 0 and not included.
We know r = 0. Let ∆ 1 and ∆ 2 be typing context such that ∆ ′ = (∆ 1 , x : [A] 0 , ∆ 2 ). The last derivation is rewritten as follows: We compare the typing contexts between the conclusion of the lemma and that of the above derivation to obtain the following: By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we know Γ = (Γ ′′ |[∆2]0 + [∆ 1 ] 0 ) and Γ ′ = (Γ ′′ |[∆2]0 + [∆ 2 ] 0 ). We then apply the induction hypothesis to the premise of the last derivation and reapply (weak) as follows: where we choose ∆ 1 + ∆ + ∆ 2 as the newly added typing context. Since x is unused by t, thus note that [t ′ /x]t = t, the conclusion of the above derivation is equivalent to the conclusion of the lemma except for typing contexts. Finally, we must show that ( ] 0 ) (∵ + associativity and commutativity) Thus, we obtain the conclusion of the lemma.
• Suppose (x : [A] r ) / ∈ [∆ ′ ] 0 Let Γ 1 and Γ 2 be typing context such that Γ ′′ = (Γ 1 , x : [A] r , Γ 2 ). The last typing derivation of (weak) is rewritten as follows: We then compare the typing context between the conclusion of the lemma and that of the that of above derivation as follows: By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we know We then apply the induction hypothesis to the premise of the last derivation and reapply (weak) as follows: The conclusion of the above derivation is equivalent to the conclusion of the lemma except for the typing contexts. Finally, we must show that ) (∵ + associativity and commutativity) Thus, we obtain the conclusion of the lemma.
-Case (ver) We compare the typing contexts between the lemma and the above conclusion as follows: We then reorganise the typing context i∈Ix ( Thus, we obtain the following: Therefore, By Lemma 4, there exists typing contexts Γ ′ i and Γ ′′ i such that: Thus, we obtain the following: By the commutativity of ",", we can take Γ and Γ ′ arbitrarily so that they satisfy the above equation. So here we know Γ = Γ ′ i , Γ ′ = Γ ′′ i , and r = i∈Ix ({l i } ⊗ r i ). We then apply the induction hypothesis to the premise whose typing context contains x. Here, we define a typing context ∆ i as follows: By using ∆ i , we reapply (ver) as follows: by the definition of substitution, the above conclusion is equivalent to the conclusion of the lemma except for typing contexts. Finally, we must show that ( Thus, we obtain the conclusion of the lemma. -Case (veri) This case is similar to the case of (ver).
-Case (extr) In this case, we apply the induction hypothesis to the premise and then reapply (extr), we obtain the conclusion of the lemma.
-Case (sub) The last derivation is rewritten as follows: (sub) We then apply the induction hypothesis to the premise of the last derivation to obtain the following: The typing context of the above conclusion can be transformed as follows: The last equational transformation holds by the following equation 2. Let Γ 3 and Γ ′ 3 be typing contexts that satisfy the following: , Let r 3 and r ′ 3 be typing contexts such that r 3 = r⊗r ′ 3 and stisfy the following: Thus, we obtain the following equation.
Applying all of the above transformations and reapplying (sub) to the expression 1, we obtain the following: (sub) The conclusion of the above derivation is equivalent to the conclusion of the lemma except for the typing contexts. Finally, we must show that The last transformation is based on the following equation that can be derived from the definition 6.
Thus, we obtain the conclusion of the lemma.
This case is similar to the case of (x : The last derivation is rewritten as follows: (sub) We apply the induction hypothesis to the premise and then reapply (sub), we obtain the conclusion of the lemma.

C.4 Type Safety
Lemma 9 (Inversion lemma). Let v be a value such that Γ ⊢ v : A. The followings hold for a type A.
-A = Int =⇒ v = n for some integer constant n.
and some labels l i ∈ r.
t for some pattern p and term t.

Lemma 10 (Type safety for default version overwriting).
For any version label l: Proof. The proof is given by induction on the typing derivation of Γ ⊢ t : A. Consider the cases for the last rule used in the typing derivation of assumption.
-Case (int) This case holds trivially because n@l ≡ n for any labels l.
-Case (var) This case holds trivially because x@l = x for any labels l.
-Case (abs) By induction hypothesis, there exists a term t 1 @l such that: We then reapply (abs) to obtain the following: Thus, note that (λp.t 1 )@l ≡ λp.(t 1 @l), we obtain the conclusion of the lemma.
-Case (app) By induction hypothesis, there exists terms t 1 @l and t 2 @l such that: We then reapply (app) to obtain the following: Thus, note that (t 1 t 2 )@l ≡ (t 1 @l) (t 2 @l), we obtain the conclusion of the lemma.
-Case (let) By induction hypothesis, there exists terms t 1 @l and 2@l such that: We then reapply (let) to obtain the following: Thus, note that (let [x] = t 1 in t 2 )@l ≡ let [x] = (t 1 @l) in (t 2 @l), we obtain the conclusion of the lemma.
-Case (weak) By induction hypothesis, we know the following: We then reapply (weak) to obtain the following: Thus, we obtain the conclusion of the lemma.
-Case (der) By induction hypothesis, there exists terms t@l such that: We then reapply (der) to obtain the following: Thus, we obtain the conclusion of the lemma.
-Case (pr) This case holds trivially because [t]@l ≡ [t] for any labels l.
-Case (ver) This case holds trivially because {l i = t i }@l ≡ {l i = t i } for any labels l.
-Case (veri) In this case, there are two possibilities for the one step evaluation of t.
We can apply the default version overwriting as follows: In this case, we can derive the type of ⟨l i = t i | l⟩ as follows: Thus, we obtain the conclusion of the lemma. • Suppose l / ∈ {l i }. We can apply the default version overwriting as follows: -Case (extr) By induction hypothesis, there exists a term t 1 @l such that: We then reapply (extr) to obtain the following: Thus, note that (t 1 .l k )@l ≡ (t 1 @l).l k , we obtain the conclusion of the lemma.
-Case (sub) By induction hypothesis, there exists a term t@l such that: We then reapply (sub) to obtain the following: Thus, we obtain the conclusion of the lemma.
We can apply (E-ex1) as follows: Also, we get the following derivation for v.
By Lemma 10, we know the following: Finally, we can rearrange the typing context as follows: Here, we follow the same manner as for the derivation of [t ′′ ] (which may use (weak) and (sub)) to get [Γ ] from r · [Γ ′ ]. Thus, we obtain the conclusion of the lemma.
We can apply (E-ex2) as follows: Also, we get the following derivation for v.
By Lemma 10, we know the following: Finally, we can rearrange the typing context as follows: Here in the multiple application of (sub), the second premise compares the resources of j-th versioned assumption between the first premise and conclusion. Also, we follow the same manner as for the derivation of {l i = t i } (which may use (weak) and (sub)) to get . Thus, we obtain the conclusion of the lemma.
Proof. The proof is given by induction on the typing derivation of t. Consider the cases for the last rule used in the typing derivation of the first assumption.
-Case (int) This case holds trivially because there are no reduction rules that can be applied to n.
-Case (var) This case holds trivially because there are no reduction rules that can be applied to x.
-Case (abs) This case holds trivially because there are no reduction rules that can be applied to λp.t 1 .
-Case (app) We perform case analysis for the ruduction rule applied last.
where t 1 = λx.t ′ 1 for a term t ′ 1 . Then we can apply (£ var ) to obtain the following: In this case, we know the typing derivation of t has the following form: , (der), or (sub) . . .
(weak), (der), or (sub) A By Lemma 7, we know the following: Finally, we can rearrange the typing context as follows: (weak), (der), or (sub) : A Here, there exists a derive tree to get Γ 1 + Γ 2 from Γ ′ 1 + Γ 2 as for the derivation of λx.t ′ 1 which may use (weak), (der) and (sub). By choosing t ′ = [t 2 /x]t ′ 1 , we obtain the conclusion of the theorem.
• Case (E-abs2) In this case, we know the typing derivation of t has the following form: A Therefore, we can construct the derivation tree for t ′ as follows.
A Hence, we have the conclusion of the theorem.
-Case (let) Thus, by choosing t ′ = t ′′ , we obtain the conclusion of the theorem.
-Case (der) In this case, t does not change between before and after the last derivation.
The induction hypothesis implies that there exists a term t ′′ such that: We then reapply (der) to obtain the following: Thus, by choosing t ′ = t ′′ , we obtain the conclusion of the theorem.
-Case (pr) This case holds trivially because there are no reduction rules that can be applied to [t ′′ ].
-Case (ver) This case holds trivially because there are no reduction rules that can be applied to {l i = t i }.
-Case (veri) In this case, the only reduction rule we can apply is (E-veri).
By Lemma 10, we obtain the following: Finally, we can rearrange the typing context as follows: This case holds trivially because there are no evaluation rules that can be applied to x.
-Case (abs) This case holds trivially because there are no evaluation rules that can be applied to λp.t 1 .
-Case (app) In this case, there are two evaluation rules that can be applied to t.
• Suppose the evaluation rule matches to [·]. We perform the case analysis for the last ruduction rule. * Case (E-abs1) We know the evaluation of the assumption has the following form: In this case, we know the typing derivation of t has the following form: A Therefore, we can construct the derivation tree for t ′ as follows.
A Hence, we have the conclusion of the theorem.
• Suppose the evaluation rule matches to E t.
We know the evaluation of the assumption has the following form: . By induction hypothesis, we know the following: We then reapply (app) to obtain the following: Thus, we obtain the conclusion of the theorem.
-Case (let) In this case, there are two evaluation rules that we can apply to t.
• Suppose the evaluation rule matches to [·]. We know the evaluation of the assumption has the following form: By Lemma 1, we know the following: Thus, we obtain the conclusion of the theorem. • Suppose the evaluation rule matches to let [x] = E in t.
We know the evaluation of the assumption has the following form: . By induction hypothesis, we know the following: We then reapply (let) to obtain the following: Thus, we obtain the conclusion of the theorem.
-Case (weak) In this case, t does not change between before and after the last derivation.
The induction hypothesis implies that there exists a term t ′ such that: We then reapply (weak) to obtain the following: Thus, we obtain the conclusion of the theorem.
-Case (der) In this case, t does not change between before and after the last derivation. The induction hypothesis implies that there exists a term t ′ such that: We then reapply (der) to obtain the following: Thus, we obtain the conclusion of the theorem.
-Case (pr) This case holds trivially because there are no evaluation rules that can be applied to [t ′′ ].
-Case (ver) This case holds trivially because there are no evaluation rules that can be applied to {l i = t i }.
-Case (veri) In this case, the only evaluation rule we can apply is evaluation for [·]. We know the evaluation of the assumption has the following form: E-veri ⟨l i = t i | l k ⟩ Y t k @l k ⟨l i = t i | l k ⟩ t −→ t k @l k By Lemma 1, we know the following: Thus, we obtain the conclusion of the theorem.
-Case (extr) In this case, there are two evaluation rules that we can apply to t.
• Suppose the evaluation rule matches to [·]. We know the evaluation of the assumption has the following form: E-ex1 or E-ex2 t 1 .l k Y t ′ By Lemma 1, we know the following: Thus, we obtain the conclusion of the theorem. • Suppose the evaluation rule matches to E.l.
We know the evaluation of the assumption has the following form: . By induction hypothesis, we know the following: We the reapply (extr) to obtain the following: Thus, we obtain the conclusion of the theorem.
-Case (sub) In this case, t does not change between before and after the last derivation. The induction hypothesis implies that there exists a term t ′ such that: We then reapply (sub) to obtain the following: Thus, we obtain the conclusion of the theorem.
Proof. The proof is given by induction on the typing derivation of t. Consider the cases for the last rule used in the typing derivation of the assumption.
-Case (int) This case holds trivially because value n.
-Case (var) This case holds trivially because x : A cannot be ∅.
-Case (abs) This case holds trivially because value λp.t.
-Case (app) There are two cases whether t 1 is a value or not.
• Suppose t 1 is a value. By the inversion lemma (9), we know that there exists a term t ′ 1 and t 1 = λp.t ′ 1 . Thus, we can apply two rules to t as follows.
• Suppose t 1 is a value. (t 1 = v 1 ) By Lemma 11, we know the following: Thus, we obtain the conclusion of the theorem. • Suppose t 1 is not a value.
There exists a term t 1 such that: Also, we can apply an exaluation rule for extraction to t.
Thus, by choosing t ′ = t ′ 1 .l k , we obtain the conclusion of the theorem.
-Case (sub) In this case, t does not change between before and after the last derivation. Thus, by induction hypothesis, we obtain the conclusion of the theorem.
Proof. Straightforward by induction on the derivation of Σ ⊢ r : Labels.
Proof. Straightforward by induction on the derivation of Σ ⊢ A : Type. The proof uses Lemma 12.
Proof. Straightforward by induction on the derivation of Σ ⊢ Γ . The proof uses Lemmas 12 and 13.
Proof. Straightforward by induction on the derivation of Σ ⊢ R. The proof uses Lemma 12.