Abstract
To let expressions evaluate to no or many objects, most object-oriented programming languages require the use of special constructs that encode these cases as single objects or values. While the requirement to treat these standard situations idiomatically seems to be broadly accepted, I argue that its alternative, letting expressions evaluate to any number of objects directly, has several advantages that make it worthy of consideration. As a proof of concept, I present a core object-oriented programming language, dubbed Num, which separates number from type so that the type of an expression is independent of the number of objects it may evaluate to, thus removing one major obstacle to using no, one, and many objects uniformly. Furthermore, Num abandons null references, replaces the nullability of reference types with the more general notion of countability, and allows methods to be invoked on any number of objects, including no object. To be able to adapt behavior to the actual number of receivers, Num complements instance methods with plural methods, that is, with methods that operate on a number of objects jointly and that replace static methods known from other languages. An implementation of Num in Prolog and accompanying type and number safety proofs are presented.
1 INTRODUCTION
The evolution of programming languages is marked by the introduction of abstractions suited to rid programming of accidental complexity, that is, of the complexity imposed by the underlying computing machinery rather than the subject of programs [10]. One such complexity, the different coding idioms required when dealing with many or no objects instead of one, has largely resisted abstraction in programming languages, even though other languages, notably modeling languages, handle none, one, and many uniformly [57].
From a traditional viewpoint [40], the different encoding of none, one, and many objects is natural: in programs, objects are represented by expressions, and expressions are regarded as functions, meaning that unless they are partial, they evaluate to precisely one object. In this functional view, there is no place for no or many objects—both must be encoded as special objects (including null) or containers (tuples, arrays, or collections), whose handling leads to the said coding idioms. Uniformly viewing expressions as relations, on the other hand, would mean that they could evaluate to any number of objects, including one and none.
While some programming languages, functional ones in particular, have come some way in reducing the programming overhead required for handling no or many objects (using, e.g., maybe and list monads [65] or streams [4]), their solutions introduce an indirection that has its own price tag. Specifically, these solutions usually mean (1) a change of type from the object type to the container (or wrapper) type, demoting the content type to a subscript (parameter) of the container type, and (2) the necessity of dealing with the introduced indirection, for instance by wrapping and unwrapping objects. It is instructive to note how this parallels the introduction of pointer types and the creation and dereferencing of pointers for the implementation of dynamic and recursive data structures, which was largely abstracted away by object-oriented programming languages such as Smalltalk and Java, apparently not to their detriment.
It follows that while the case analyses required to handle none, one, and many may take on more convenient forms, they still exist in programs, whereas a language-level abstraction would remove them entirely and hide them in the language implementation. There are pros and cons to both approaches: while the former grants greater flexibility, the latter lowers the accidental complexity of standard cases to zero. In this present work, I pursue the latter, and explore a relational, rather than functional, interpretation of expressions that caters for the uniform treatment of none, one, and many. For this, I let expressions evaluate to any number of objects, without these objects being contained in any way; in my words, I let them evaluate to containerless plurals.
Contribution. With this work, I deliver the theoretical underpinnings of my earlier works [56, 57, 59], which laid out the general ideas of programming with numbers of objects, first by inverting the relationship between container and content [56, 59], and later by dropping containers entirely [57]. In the present work, I show that, as previously only claimed, with the strict separation of type and number, two largely independent matters of programming can be set apart at the language level: objects being of the right type vs. objects being in the right number. I will do this by formally introducing a core object-oriented programming language, dubbed Num, which features number as a mode that is parallel to, rather than encoded in, type, and which is supported by notions of static number checking and number safety. From an object-oriented programming perspective, number extends the nullability of reference types to countability; at the same time, introduction of a number specifier one (which was previously only proposed [57]) makes sure that values (i.e., instances of value types, such as integers and booleans) can remain uncountable and, specifically, do not become nullable. In addition, Num distinguishes ordinary, or singular, methods which, when invoked (in one call) on a number of objects, are executed on each one of them (where as usual, the receivers are accessible in the bodies of the methods through
Embedding. This work is part of a larger effort aimed at making object-oriented programming more relational. It is motivated by the observation that in software modeling, objects are commonly linked through relations rather than fields or attributes (functions), and that contemporary mainstream object-oriented programming languages (with the exception perhaps of the .NET languages with their language-integrated querying [36]) rely on complex and brittle frameworks for bridging the two worlds [50]. While a full integration of relations and object-oriented programming would require bi-directionality and coverage of higher degree relations, this work focuses on a prerequisite of such a venture: It introduces the handling of plurals through typed and numbered to-many pointers.
Organization. I begin in Section 2 with a motivating example showcasing some of the fundamental disparities programmers must live with when dealing with one object, on the one side, and many objects, on the other. In Section 3, I introduce the notion of numbers of objects that is central to this work, first by motivating it ontologically and then by making it precise. Section 4 provides the intuition behind programming with numbers of objects and shows how it serves the elimination of the disparities identified in Section 2. Section 5 then formally defines the language Num, sketches its implementation in Prolog, and shows how its largely independent type and number systems make Num programs both type and number safe. The design decisions that shaped Num are critically discussed in Section 6, which also reproduces some of the findings of an earlier case study [59]. Section 7 discusses a significant portion of the abundant work by others that is related to, and has influenced, mine. The full specification of the semantics of Num and the proof of its type and number safety are deferred to the appendix.
2 MOTIVATION
As noted in the introduction, this work is motivated by the long-term goal of replacing functions with relations in object-oriented programming. It can hence be viewed from a relational and an object-oriented perspective.
The Relational Perspective. While object-oriented programming is largely functional in character (a field maps an object to, and a method returns, no or one object), object-oriented modeling, and data modeling in general, are typically relational: objects (or entities) are connected through relations (or relationships, or associations), which map one object in one place of a relation to a number of objects in another. To capture the specifics of a domain, relations can be specialized to (partial) functions by so-called mapping constraints [35] which, for binary relations, have the form N:1, 1:N, or 1:1, meaning that a relation is in fact a (partial) function mapping an object to at most one object in each place constrained by 1. In this light, object-oriented programming as we know it is a specialization of a more general object-relational programming paradigm in which all relations are constrained to binary, uni-directional, and N:1 (the latter because generally, an arbitrary number of objects can relate to, through a field or method, any one object).
Navigation of relational data relies on a join operator which, in an object-oriented setting, corresponds to field access (which, in case of ternary or higher-degree relations, may be qualified with other objects) [32]. However, while relational join is generally agnostic of mapping constraints (an N:M relation is navigated through the same expression as an N:1 relation), this is not the case in object-oriented programming: here, to implement a (unidirectional) N:M relation, the implementing field must hold a collection, and collection-valued fields are generally navigated differently (using loops or mapping) than single-object valued fields. The same holds for updating: while updating a relation in a relational language uniformly corresponds to adding or removing tuples, updating fields depends again on whether the field is prepared to hold no or one object, or any number of objects contained in a collection. While seamlessly integrating the navigation and updating of relations in object-oriented programming would require a unified handling of no, one, and many objects, it turns out that such a unification stands beautifully on its own: it corresponds to the introduction of the plural to a paradigm that so far offered only the singular for expression.
The Object-Oriented Programming Perspective. The distinction between one and many objects is a fundamental one that can be found in every non-trivial program. One would therefore expect that the support of object-oriented programming languages for this distinction is declarative, or in other ways sufficiently abstract. However, this is not the case—instead, the necessary differences in code for handling one and many objects are marked by technical detail and appear surprisingly ad hoc. That this is indeed the case is demonstrated by the example of an implementation of the Observer pattern in Java as shown in Figure 1 which, although somewhat contrived, exacerbates nine marked differences between a subject having one (the singular case, left) and many (the plural case, right) observers:
Fig. 1. Left: Implementation of the Observer pattern with a single observer per subject. Right: Same program as left, with many observers (and many subjects in the main code).
(1) | Number Determines Type. In line 25 of Figure 1, left, we have the declaration of a variable | ||||
(2) | Number Governs Subtyping. To a variable declared to hold a single object of a given type, subtyping means that we can assign it objects of the type’s subtypes, as is exploited by the method call in line 26, left (which assigns an actual parameter having type | ||||
(3) | Number Governs Encoding of “no Object”. For a variable capable of holding a single object, that it holds no object is encoded by the value | ||||
(4) | Number Governs Null-Safety. Closely connected is the fact that technically, the safety of the method call in line 14, left, depends on the assertion of line 4 —if | ||||
(5) | Number Shapes Chaining. While number shapes member access as sketched above, the differences in chained member access between singulars and plurals are even greater. For instance, leaving null-safety aside (which, strictly speaking, affects both sides, as long as variables holding containers can also hold | ||||
(6) | Number Dictates Encapsulation Strategy. Whereas it is standard practice that fields holding single objects can be read and written through getters (line 11, left) and setters or constructors (line 5), collections, especially if representing relationships to many objects, are usually considered representation objects [44] and are therefore to be protected by copying, as in lines 5 and 11 f., right. | ||||
(7) | Number Governs Sharing. Through the indirection that comes with the use of containers, two variables can be made to share the same storage location. An example of this is given in lines 6 –8, right, where after the assignment of line 6, the variables | ||||
(8) | Number Governs Call Semantics. While in Java and many other object-oriented programming languages, method calls are by-value, due to the sharing through containers noted above, when collections are passed calls are effectively by-reference. For instance, the constructor invocation | ||||
(9) | Number Determines the Meaning of the | ||||
Surely, some of these disparities can be leveled out in other object-oriented languages with different typing disciplines: for instance, Scala has declaration-site variance so that using its covariantly typed immutable collections reduces or bypasses the above semantic differences 2 and 6–9, and languages with implicit coercions can address difference 1 to some degree. Other language features (such as type classes) allow the reduction of syntactic differences, and the use of higher order functions even for primitive operations such as assignment or member access may push all differences to a library. However, rather than trying to camouflage the difference between one and many using mechanisms generally suited for language extension, the purpose of this work is to acknowledge its fundamental nature, by giving it a central place in the core language, as a (new) mode number complementing type in declarations.
3 NUMBERS OF OBJECTS
Before I introduce, in Section 4, numbers of objects as a programming construct suited to rid programs of the discontinuities identified in Section 2, I invite the reader to a brief ontological excursion intended to give a deeper rationale for some of my design decisions, and introduce the language of plurals on which my formal capture of the language Num in Section 5 depends.
3.1 Ontological Considerations
3.1.1 Containerless Plurals.
When Georg Cantor conceived sets, he spoke of them as “jedes Viele, welches sich als Eines denken läßt” [13, p. 587], i.e., as “any many which can be thought of as a one” (my translation). Indeed, a mathematical set reifies a number of (mathematical) objects as one object, making the set subjectable to much of the same mathematical machinery as its members. Before this reification, many objects were just that: many objects.
While sets have proved extremely useful, not only in mathematics, we sometimes need to talk about many objects outside of sets or some other sort of container. For instance, the arithmetic equation x2 = 4 has two solutions, − 2 and 2, but the solution is not the set { − 2, 2}, nor is it any other single mathematical object—in fact, there is no one solution. Mathematically, this plurality is explained by the fact that the inverse of x2 is a relation mapping 4 to − 2 and 2 (and not a function mapping 4 to { − 2, 2}); we note here that − 2 and 2 is different from { − 2, 2} and, more generally, that the contents of a set are not a set (see Section 7.1.1 for a discussion of Eric Hehner’s theory of bunches [27], which is rooted in the same observation).
In programming, occurrences of many objects are usually reified as one using some sort of collection. Just like the introduction of the null pointer, from the programming language perspective this is very convenient, because it ensures that every expression evaluates to precisely one value —sorting out the differences is left to programs. At the program text level, however, many objects may occur, for instance, as arguments to functions or in array initializers; and yet, these plurals (occurrences of many) are typically not first-class citizens of a programming language, and if they are, they are usually forced into some kind of container (typically a list or an array; e.g., the var args of Java).
Conceptually, forcing occurrences of many objects into a container just for the sake of being able to handle them seems awkward: for instance, I have a wife, two parents, and three children —I do not have a pair of parents, nor a set of children, and harmonizing the situation by saying that I am married to a singleton elevates weirdness to the norm. What is a numerical difference, or difference in number, in natural and also many modeling languages (including Entity-Relationship Diagrams [15] and UML [45]; cf. Section 7.5) is a difference in type and degree of indirection in programming languages. Arguably, this is a complexity imposed by the language, or an “accidental complexity” [10].
3.1.2 Number as a Grammatical Feature.
In the English language, like in many others, the difference between one and many manifests itself in the distinction between singular and plural forms, where singular and plural are expressions of the grammatical category, or feature, number. In English, number is a feature of nouns, pronouns, and verbs, and English grammar requires that the subject of a sentence (a noun or a pronoun) and its predicate (a verb) must have the same number. Plural subjects can be constructed from singular nouns using and (as in “ − 2 and 2” above) and some other conjunctions; conversely, many (logical) subjects can be represented by a collective noun, whose number is singular (as in “the set of − 2 and 2”).
While programming languages do not provide for plurals,2 or “any many that are not thought of as a one”, the metalanguages of programming languages make ample use of them: the notations a*, a1 … an, and \( \overline{a} \) are ubiquitous in formal language specifications, where they stand for a number of items of the kind of a, that is, for a plural (also referred to as “unenclosed sequence” [55]; cf. Section 7.1.2). It seems reasonable to expect that what is useful for a metalanguage is useful for its object languages also.
3.1.3 Countables and Uncountables.
The linguistic distinction between singular and plural is not universal—it applies only to the so-called countables. While the conditions for countability vary from language to language and usually involve context, as a rule of thumb, countability requires identity—only things that have identity we can count, and only of those we can have many. For instance, when we say that x2 = 4 has two solutions, this presupposes that we can identify each solution (and that the two solutions are different). Uncountables, on the other hand, are not identifiable; we cannot refer, for instance, to two rains. Yet, identity, and hence countability, can be added to uncountables by wrapping: for instance, we can refer to two occasions of rain.
Programming languages may also distinguish things that have identity and things that do not. For instance, the Java programming language distinguishes between primitive values and reference values [2], where reference values are usually equated with the identities of objects, something primitive values do not have.3 While regarding numbers, characters, and other primitive values as uncountable4 may seem ill-devised, we already have this situation in all object-oriented programming languages in which only reference types are nullable—for these languages, countability is a generalization of nullability, namely, from covering zero or one to covering any number. Consistently extending nullability to primitive (or value) types already requires a significant reworking of language semantics, as demonstrated by its introduction to C♯ [39], which involved the adaptation of all operations on primitive values to cover the absence of a value (cf. Section 7.2), and extending nullability of primitive types to countability will almost inevitably lead to array programming, which is however not containerless (see Section 7.2.5). Hence, in this present work, I adopt the distinction of countables and uncountables in that I do not cater for numbers of primitive values, unless they are given identities through boxing (cf. Section 6.2).5
3.1.4 Linguistic Note.
Dealing with many objects without making (implicit) use of containers is a mental exercise. To invite the reader to participate in this exercise, I use the term “number of objects” for containerless plurals, or occurrences of any number of objects that are not thought of as a one, basically because the word “number” is not a collective noun like “bunch” [27], “sequence” [55], or “multitude” [59]. As part of the exercise, all uses of the term “number of objects” in this writing have the (grammatical) number plural, so as to make clear that the term is not used collectively (that the many are not treated as a one). When I use the single word “number”, I always mean the grammatical category number, with expressions yet to be introduced; to denote the result of counting objects (the “dynamic number”), I will use the word “count”. The word “number” has the advantage over the word “multiplicity” (which I used in prior works [56, 57, 59]) of being both a noun and a verb, allowing me to use it in analogy to “type”; in particular, I will use “numbering” to refer to the assignment of numbers to expressions and to number checking, and I will use “well-numbered” to express that whatever this term attributes, abides by the rules of numbering.
3.2 Metalanguage for Numbers of Objects
To write about numbers of objects N, I use the grammar \( \begin{equation*} N ::= \epsilon \ |\ o\,N\, , \end{equation*} \) where o ranges over single, countable objects and ϵ denotes zero objects, or the absence of an object (“no object”). I write o1 … on for n objects o1 through on, where the oi must be unique (pairwise non-identical) and where n may be zero (in which case o1 … on ≡ ϵ). I call n the count of o1 … on. Note that I use juxtaposition without a separator to denote numbers of more than one object; this is to avoid the impression that numbers of objects are lists in disguise (with the separator as the list constructor). Also, note that the occurrence of a single object counts as a number of objects, namely, as the special case in which the count is 1. For disambiguation, I sometimes parenthesize numbers of objects; however, one cannot have numbers of numbers of objects—there is no nesting of numbers of objects, and zero or more than one objects are not an object.6
Numbers of objects are similar to strings, one difference being that no object may occur (and hence can count) twice (where singularity is judged by object identity). While this restriction may appear arbitrary, it is justified by the above ontological motivation and by the fact that numbers of objects are used to specify relations (see the beginning of Section 2, and Section 3.2.2).
3.2.1 Operations on Numbers of Objects.
On numbers of objects N = o1 … on, I define the unary operator |o1 … on| ≔ n (meaning that |N| is the count of N). Furthermore, I define the binary operator ∝, read as “among”, as \( \begin{equation*} N \propto {o}_1\, \ldots \, {o}_{n} \qquad \Leftrightarrow \qquad N = \epsilon \quad \vee \quad N=o\,N^{\prime }\ \wedge \ \exists 1 \le i \le n\ .\ o = o_i\ \wedge \ N^{\prime } \propto {o}_1\, \ldots \, {o}_{n}. \end{equation*} \) To construct numbers of objects from numbers of objects, I define the binary operators object addition (⊕) and object subtraction (⊖) as \( \begin{equation*} N_1\oplus N_2 \quad :=\quad {\left\lbrace \begin{array}{ll}N_1 & \text{if }N_2=\epsilon \\ N_1\oplus N_2^{\prime } & \text{if } N_2 = o\,N_2^{\prime } \text{ and } o\propto N_1\\ (N_1\,o)\oplus N_2^{\prime } & \text{if } N_2 = o\,N_2^{\prime } \text{ and } o\not\propto N_1\\ \end{array}\right.} \end{equation*} \) and \( \begin{equation*} N_1\ominus N_2\quad :=\quad {\left\lbrace \begin{array}{ll}N_1 & \text{if }N_1=\epsilon \\ N_1^{\prime }\ominus N_2 & \text{if } N_1 = o\,N_1^{\prime } \text{ and } o\propto N_2\\ o\,(N_1^{\prime }\ominus N_2) & \text{if } N_1 = o\,N_1^{\prime } \text{ and } o\not\propto N_2\\ \end{array}\right.}, \end{equation*} \) respectively. Note that ϵ is the neutral element of object addition, meaning that N1⊕… ⊕Nn = ϵ for n = 0.
3.2.2 Using Numbers of Objects for Encoding Functions and Relations.
As noted in Section 3.1.1, relations can be viewed as mappings from one to a number of objects: for instance, the relation { (x, y)∣x = y2 } maps 4 to − 2 and 2. Functions are then mappings to a number of objects whose count is constrained to 1 (total functions) or ≤ 1 (partial functions), where mapping to ϵ means that the function is undefined for the argument (see also Hehner [26, p. 29] for a corresponding use of bunches).
3.2.3 Ordering of Numbers of Objects.
Even though neither “containerless plural” nor “number of objects” suggests an ordering of the denoted objects, as syntactic entities, numbers of objects are necessarily ordered (due to the linear nature of textual syntax, which is preserved operationally by the above specifications of ⊕ and ⊖). Because orderedness eliminates a source of nondeterminacy in programming with numbers of objects, and because orderedness is a prerequisite to using numbers of objects for expressing “unenclosed sequences” in the specification of Num (i.e., as constructs of the metalanguage), I will henceforth assume that numbers of objects are ordered.
4 PROGRAMMING WITH NUMBERS OF OBJECTS
Before formally defining the language Num in Section 5, I provide some basic intuition of programming with numbers of objects, by guiding the reader through a couple of examples. The examples use the syntax of Num as shown in Figure 2. In a nutshell, the reader may assume that a small core of Java has been extended with number declarations and expressions evaluating to numbers of objects, governed by static numbering that complements (standard) static typing. Specifically:
Fig. 2. Syntax of Num. The metavariables C, m, f, x, and their derivatives C′ and so on, range over class names, method names, field names, and variable names, respectively. Superscripts C and κ are worked out by typing.
— | In declarations of variables, fields, and methods, reference (or class) types C (“countables”, as opposed to the value types | ||||
— | The number specifiers of Num are: | ||||
— | On number specifiers the reader may assume a partial order ≤ as the smallest reflexive and transitive relation that includes \( \texttt {-}\le \texttt {?} \), \( \texttt {!}\le \texttt {?} \), and \( \texttt {?}{} \le \texttt {*}{} \) (see Table 1(a) for a depiction); together with the subtype relation, this partial order governs assignment compatibility. | ||||
— | In method definitions, the name of a method is followed by a number specifier η, where | ||||
— | Num has no | ||||
— | Num has no | ||||
— | This is complemented by | ||||
— | Num has no constructors. Instead, all fields are initialized, upon creation of an object (using \( \texttt {new}~ C \)), to the values of initialization expressions provided at their definition site. | ||||
Note that the metalanguage used in Figure 2 for the specification of Num’s syntax includes the language of numbers of objects as introduced in Section 3.2, accounting for “unenclosed sequences” [55] (cf. Section 7.1.2) and improvising the insertion of separators in places as usual. For instance, K1 … Kn stands for a number of classes (including none).
The basic idea of programming with numbers of objects is that
— | instead of evaluating to one object, expressions having a countable type evaluate to numbers of objects; that | ||||
— | their count (“dynamic number”) is statically constrained by a number system (analogous to how the dynamic types of the objects are constrained by a type system); and that | ||||
— | occurrences of none, one, and many objects are handled alike. | ||||
For member access, this means that the access is lifted from one object to any number of objects (including none), collecting the results (if any) as a number of objects; for assignment, this means that the pointers to the number of objects an expression evaluates to are copied into the variable (field, formal parameter) on the left-hand side of the assignment (in analogy to how for standard assignment of reference values, one pointer is copied). For fields having countable types, numbering means that they are no longer interpreted as functions mapping the owner of the field to precisely one object or
4.1 Scrapping Containers
Using Num for the motivating example from Section 2, the noted differences in handling none, one, and many objects disappear, and materialize in declarations as differences in number, as highlighted in Figure 3. Specifically:
Fig. 3. Implementation of the two versions of the Observer pattern of Figure 1 in Num (semantic differences underlined). Left: with exactly one observer per subject. Right: Same program as left, with any number of observers (and subjects in the main code). For fairness of comparison with Java, return no Object has been elided from Object- (void) methods, and temporaries have been declared inline (not as formals, as would be required by Num; see text). assert is not formalized, but implemented in Num.
(1) | Number Does not Determine Type. The declarations of the fields | ||||
(2) | Number Does not Govern Subtyping. Since number has been separated from type, the two assignments | ||||
(3) | Encoding of “no Object” Is Universal. Expressions evaluating to no object evaluate to just that (i.e., a number of objects with count 0), independently of their static number being | ||||
(4) | Null-Safety is Universal. Since there is no difference in representation of no object between the singular and the plural case, and because accessing members on no object is safe (see above), there is no dependence of null-safety on number like that worked out in Figure 1. In addition, the | ||||
(5) | Number Does not Shape Chaining. Just like single method invocations are syntactically indistinguishable for singular and plural receivers (see item 1 above), the shape of chained method invocations does not depend on the return number of any of the involved methods. Hence, the invocation expression of lines 27 f. in Figure 1, right, is replaced by | ||||
(6) | Encapsulation Strategy is Universal. Because of the containerlessness and because of the copy semantics of the assignment of many objects (cf. above), there is no representation that is specific to the plural case and that needs to be protected by copying or some other means. | ||||
(7) | Non-Sharing is Universal. For the same reasons, there is no sharing that is specific to the plural case—numbers of objects are never shared (in the sense that they are aliased as a whole; individual objects can still be aliased). | ||||
(8) | Call Semantics is Universal. For the same reasons again, call semantics is by-value in all cases. | ||||
(9) | The Meaning of | ||||
Note that for the singular, case, the assertion in line 4, left, has become obsolete, because the cast to number
4.2 Plural Methods
While invoking a singular method on a number of objects means that it is invoked on each object individually, invoking a plural method means that it is invoked on the objects jointly. Specifically, in the bodies of plural methods,

where
While invocations of singular methods are dispatched individually on the dynamic type of each object among the receiving number of objects, plural methods are necessarily dispatched on the static type of the receiver expression (this is necessary because if the count of the receiver objects is 0, a dynamic type cannot be determined; for counts higher than 1, a decision would need to be made as to which method is selected, meaning that possible overridings of the method become ineffective). Plural methods are hence reminiscent of static methods in other languages, with the difference that the receiver is a number of objects (where in Java, static methods do not have a receiver, and for Smalltalk’s class methods, the receiver is a class). In fact,
Plural methods can reflect on the number of receivers a method is invoked on. This allows code like

which is reminiscent of pattern matching on number (rather than type) and where “printBlank” and “printAll” are the names of plural methods, while “printYourself” is the name of a singular method. Note how this generalizes the Null Object pattern [21] to covering many objects also.
4.3 Binary Methods
While plural methods may be used to extend the notion of binary methods [11] to numbers of objects, as in

other binary methods more naturally extend to collections of objects, or “contained plurals”. For instance, addition of numbers extends to vector addition, where each vector is a (singular) object in its own right. However, such operations are the domain of array programming, which is not the domain of this article (cf. the discussions in Sections 6.2 and 7.2.5).
There may still be cases in which one wants to lift a method invocation over pairs of objects formed from the cross product of two numbers of objects, where one is the receiver of the corresponding method and the other its argument. Such can be implemented in Num using double dispatching [30], as in

Here, class
4.4 Implementing Iterable Collections with Numbers of Objects
While it would seem natural to make numbers of objects iterable, the iteration that is inherent in method invocation on numbers of objects is generally sufficient. That this is indeed the case is demonstrated by the following bridge from numbers of objects to collections (implemented as a linked list), which can then be made iterable as usual:

This code equips
Note that while
4.5 Maps, Filters, and Folds
The collection APIs of languages like Smalltalk, C♯, Scala, or Java (the latter via streams) provide not only for mapping over collections (as Num does natively for all numbers of objects), but also for filtering, folding, and other convenient higher-order operations that replace for boilerplate code. For consistency (and depending on language), some of these operations are offered for optionals also. In Num on the other hand, plurals have the type of their constituting objects, so that filters and folds must be members of (or implemented in) these types. For instance, filtering objects of type

in class

5 THE LANGUAGE NUM
To work out the details of introducing numbers of objects to programming, I define static and dynamic semantics for Num (with syntax shown in Figure 2 of Section 4) and describe the implementation of an interpreter of Num in Prolog. Num builds on Middleweight Java (MJ) [7], a core calculus for Java adding control structures (including blocks), updatable variables (including fields), object identity and the null pointer to Featherweight Java (FJ) [29]. Num adds primitive values and numbers of objects to MJ, but drops blocks and constructors (which do not contribute to the essence of Num), and discards null pointers. Note that, as for the specification of syntax in Figure 2, I will use the language of numbers of objects from Section 3.2 in the metalanguage of the specification of semantics also.
5.1 Preliminaries
The syntax of Num places a number of restrictions on programs that are common in core calculi such as FJ [29], MJ [7], ClassicJava [20], or Bali [42]:
For the following specifications of the static and dynamic semantics of Num, it is assumed that its programs satisfy the following sanity constraints, which I will not formalize, but which are nevertheless necessary (together with well-typedness and well-numberedness as defined below) for a program to be well-formed:
— | Class names must be unique. | ||||
— | The superclass relation established by class definitions must be acyclic. | ||||
— | Fields must not be hidden in subclasses (by using the same names). | ||||
— | The names of the variables serving as the formal parameters of a method must be pairwise different, and must differ from “this”, “these”, “rec”, and “ret”. | ||||
— | Methods must not be overloaded. | ||||
For the conditions of overriding, see Sections 5.2.1 and 5.2.2.
As usual, I also define some data structures and auxiliary functions that support the specification of the semantics of Num. Specifically, I represent type and number environments as well as heaps and variable stores as maps, where a map \( \mathcal {M} \) is a number of mappings k↦w of keys k to values w, where the keys must be pairwise distinct. For \( \mathcal {M}= (k_1\! \mapsto \! w_1)\ldots (k_n\! \mapsto \! w_n) \), I define \( \mathit {dom}(\mathcal {M}) := {k}_1\, \ldots \, {k}_{n} \) (note the use of numbers of objects in place of sets; for dom, this means that it is a relation; cf. Section 3.2.2). I write \( \mathcal {M}[k] \) for looking up a map \( \mathcal {M} \) under key k, yielding the w that k maps to; I write \( \mathcal {M}[k \mapsto w] \) for updating a map \( \mathcal {M} \) at the position k, yielding a new map \( \mathcal {M}^{\prime } \) such that \( \mathcal {M}^{\prime }[k^{\prime }]=w \) if k′ = k and \( \mathcal {M}^{\prime }[k^{\prime }]=\mathcal {M}[k^{\prime }] \) otherwise. For expanding \( \mathcal {M} \) with k↦w, where \( k\not\propto \mathit {dom}(\mathcal {M}) \), I write \( (k \mapsto w)\,\mathcal {M} \) (note again that this is genuine syntax of numbers of objects, as introduced in Section 3.2; specifically, ϵ denotes “no mappings”, i.e., the empty map).
To access the various parts of the definition of a program P, I write P[C] to select the class definition \( K = \texttt {class}\;C\;\texttt {extends}\;C^{\prime }\; {\lbrace } \cdots {\rbrace } \) from P and K[m] to select the method definition \( \rho \; m \; \eta \; {(}\cdots {)\;\lbrace }\cdots {\rbrace } \) from K. Based on these selectors, I define lookup functions as shown in Figure 4:
Fig. 4. Lookup functions. Each is defined as the smallest function satisfying its given rules. Note that some functions are partial (i.e., undefined for certain arguments; see text).
— | fields(C) and method(C.m) retrieve the fields defined or inherited by class C (represented as a number of objects, including ϵ meaning “no field”) and the method m defined or inherited by C, respectively. | ||||
— | rng(C.f) and sig(C.m) retrieve the range ρ of field f and the signature η ρ1 … ρn↦ρ of method m defined or inherited by class C. | ||||
— | Ranges ρ are projected to their components by typ(·) (selecting types) and num(·) (selecting numbers); both are overloaded to map method signatures to their type and number components, respectively. Note that for uncountables, num(·) maps ρ to ηϵ rather than ϵ (which would express undefinedness; cf. Section 3.2.2); this reification of “no number” is required by the application of num(·) to method signatures, from which “no number” must be retrievable. | ||||
— | params(C.m) and body(C.m) retrieve the formal parameters and the statements from the body of method m defined or inherited by class C. | ||||
Note that P[C], K[m], and the functions depending on them may be undefined, namely, if C or C.m are not defined in P (where undefinedness is written using ⊥ in Figure 4). Here, undefinedness is to be distinguished from evaluating to ϵ: params(C.m) = ϵ means that C.m has no parameters, which is a regular case, whereas params(C.m) = ⊥ means that C.m does not exist, which may cause “stuckness” of evaluation (Section 5.5). To avoid notational clutter, P remains implicit in most specifications, here and in the following.
5.2 Static Semantics
I divide static semantics into typing and numbering. Typing includes name binding, which is required, and cannot generally be done, by numbering. Apart from this, numbering is independent from typing, as will be seen.
5.2.1 Typing and Name Binding.
We first need some additional definitions. Given the syntax of types,


Typing of Expressions. Typing judgments for expressions have the form \( \mathcal {T}\vdash e:\tau \), where the type environment \( \mathcal {T} \) is a map from variable names to types. The full set of type rules, which are mostly standard, is given in Appendix A.1; here, I will present only those that might need extra explanation:
— | The type of both ![]() | ||||
— | The lookup of fields and methods required by field access and method application labels the receivers with their static type, which is required by numbering (Section 5.2.2) and the static binding of plural method applications (Section 5.3). I only show the rule for method application here: ![]() | ||||
— | Uses of the operators ![]() | ||||
— | Type cast, number cast, and count are well-typed only for countable e; a number cast does not affect the type of an expression. Unlike Java and some of its core calculi, Num does not provide for explicit up-casts. ![]() | ||||
Type Checking of Statements. Typing judgments for statements have the form \( \mathcal {T}\vdash {s}_1\, \ldots \, {s}_{n}\;\checkmark \!\!_\tau \), where s1 … sn is a number of statements, which I call “sequence of statements” only as a concession to custom. Again, the type rules are mostly standard, and are shown in full only in Appendix A.1; here, I show the rule for

Type Checking of Definitions. The rules for type-checking the definitions of methods, fields, classes, and programs are again found in Appendix A.1; of these, only the rule for methods,

— | The typing judgment of methods, \( M\checkmark \!\!_\tau \text{ in }C \), depends on the class C in which the method is defined (“in C”). The type environment \( \mathcal {T} \) of method bodies is provided by rec (for | ||||
— | Syntax dictates that the last statement in the method body, sn, is a return statement; that its returned expression has the expected type is ensured by the type of ret in conjunction with T-Return (see above). | ||||
— | The premises of T-MethDef require that if m is also defined for the superclass C′ of C, it must have the same type signature (the override condition). Note that this applies to both singular and plural methods, thus allowing plural methods (which will be statically bound; cf. Section 4.2) to be hidden. | ||||
Note that there is no type rule that makes use of number. Typing (including name binding) is therefore independent of numbering.
5.2.2 Numbering.
For the numbering of programs, I introduce a number environment \( \mathcal {N} \), a numbering relation \( {\#} \) relating expressions e to their (static) number η, and numbering judgments, defined as the analogs of the typing environment, relation, and judgments. Because expressions e having an uncountable type cannot be assigned a number, I extend the definition of η to include ηϵ (introduced in Section 5.1 as a reification of “no number”); following Section 3.2.2, \( e{\#}\eta _\epsilon \) means that e has no number (the numbering relation does not map e to a number), which is a defined condition. This device lets uncountable expressions be subsumed under judgments \( \mathcal {N}\vdash e{\#}\eta \), thereby avoiding numerous case analyses based on countability.
On numbers η, Table 1(a) defines a partial order \( \lt \!{\#} \), which I call the subnumber relation. Note how including ηϵ in this relation, although somewhat ad hoc (strictly, “no number” cannot be a subnumber of “no number”), covers uncountability. The table also defines (in similarly ad hoc ways including ηϵ) how (static) numbers combine for singular method invocation as well as adding and subtracting numbers of objects, as will be needed for the numbering of corresponding expressions (see below).
Numbering of Expressions. As for typing, the full set of rules are only shown in the appendix (A.2). However, with static numbering being novel, I will explain a larger fraction of the rules here:
— | Uncountables do not have a number: ![]() | ||||
— | Numbers of ![]() | ||||
— | The number rules for ![]() | ||||
— | Both singular and plural methods (distinguished by \( \eta _0^{\prime } \), the first number of the method number signature) can be applied to receiver expressions of any number: ![]() | ||||
— | The numbers resulting from addition and subtraction are to be read from Table 1(c) and (d), respectively: ![]() | ||||
— | Only single objects (i.e., numbers of objects whose count is 1) and uncountables can be tested for identity or equality, respectively: ![]() | ||||
— | Finally, type cast, number cast, and count are only defined for countables: ![]() | ||||
Number Checking of Statements. The number checking rules for statements are almost a verbatim copy of the corresponding type checking rules from Appendix A.1, replacing \( \mathcal {T} \) and the typing relations with \( \mathcal {N} \) and the numbering relations. I therefore do not show them here; they can be found in Appendix A.2.
Number Checking of Definitions. Similarly, the rules for number checking the definitions of a program closely follow those of type checking, and are therefore deferred to Appendix A.2. The only marked difference is that the rule for method definitions,

Except for using the type annotations (superscript C) elaborated by typing (Section 5.2.1) for the purpose of member lookup, numbering of expressions does not depend on type. This comes at the expense of some redundancy, though: for instance, η ≠ ηϵ in N-TCast and N-Count is already ensured by requiring a class type in T-TCast and T-Count. Future presentations of type and number systems may therefore consider conflating the two, by specifying rules for ranges ρ rather than types τ and numbers η; here, it would have obfuscated the fact that number and type are largely orthogonal.
5.3 Dynamic Semantics
Because of the difference in their underlying relations, I divide the dynamic semantics of Num into the evaluation of expressions and the execution of statements. For both, I use big-step operational semantics, employing a big-step relation ⇒ overloaded accordingly. Big steps keep the specification of Num small, while at the same time providing for its straightforward mapping to the implementation of a Num interpreter. In fact, the rules presented here are a transcription of a Prolog implementation of such an interpreter, which will be described in Section 5.4.
5.3.1 Values.
The values v that expressions e can evaluate to are specified by the grammar \( \begin{equation*} v::= b\ | \ i\ |\ {l}_1\, \ldots \, {l}_{n}\,, \end{equation*} \) where b are booleans, i are integers, and l1 … ln are numbers of locations (or object identifiers) l representing numbers of objects on a heap \( \mathcal {H} \) (with ϵ meaning no location).
5.3.2 Context of Evaluation and Execution.
Both evaluation of expressions and execution of statements are performed in the context of a program P, the heap \( \mathcal {H} \), and a locals store \( \mathcal {L} \). Of these, the (constant) program P mostly remains implicit. \( \mathcal {H} \) and \( \mathcal {L} \) are defined as follows:
Following the example of Nipkow and von Oheimb [42], I define the dynamic semantics of expressions and statements via mutually inductive rules. I begin with the evaluation of expressions.
5.3.3 Regular Evaluation of Expressions.
Since evaluating expressions may update the heap (by creating new objects), but not the locals store (variable assignment is not an expression and method application creates a new locals store that is discarded upon method termination), regular evaluation judgments have the form \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, v\rangle \). The regular evaluation rules are mostly standard and are provided in full in Appendix A.3.1; here, I only walk through method application. Note that the evaluation order of the premises is left to right, and that all side conditions appear as premises (i.e., rules are L-attributed in the sense of Ibraheem and Schmidt [28]).
Application of plural methods (identified by \( \texttt {*} \) in the first place of their signatures, as required by the first side condition of below rule) resembles standard (object-oriented) method application, the only differences being that the lookup of the method body uses the static type of the receiver, C, and that the receiver, presented by the pseudo-variable rec, is a plural:

Application of singular methods (identified by \( \texttt {!} \) in the first place of their signatures) returning uncountables (identified by num(ρ) = ηϵ) is handled by the rule

Finally, the application of singular methods returning countables (identified by num(ρ) ≠ ηϵ) is handled by the rule

5.3.4 Exceptional Evaluation.
If evaluation runs into an exceptional condition (in Num, a bad type or number downcast exclusively), it cannot proceed regularly. Therefore, I introduce exceptional evaluation judgments, which have the form \( \langle \mathcal {H}, \mathcal {L}, e\rangle \Rightarrow \; \uparrow \!\! \mathit {Exception} \), where I call Exception a conceded exception. For instance, the regular evaluation rule for number casts,




5.3.5 Regular Execution of Statements.
Execution of statements does not return values, but may update the heap (by assigning to a field of an object) and the locals store (by assigning to a variable and by returning from a method). Regular execution judgments are therefore of the form \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow \langle \mathcal {H}^{\prime },\mathcal {L}^{\prime } \rangle \). Because the statement execution rules are standard, I have deferred all but one (shown below) to Appendix A.3.3.
5.3.6 Exceptional Execution.
While the execution of statements is never itself the source of conceded exceptions in Num, statements need to propagate exceptions coming from the evaluation of contained expressions or statements. The corresponding exceptional execution judgments have the form \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow \; \uparrow \!\! \mathit {X} \). In the case of the return statement, whose regular execution is covered by the rule


5.3.7 Running Programs.
A program P is run by taking a start expression e and evaluating ⟨ϵ, ϵ, e⟩ against P. A typical start expression would be \( \texttt {no}~ C\texttt {.main(}\texttt {no}~ \texttt {Object}\texttt {)} \), assuming that the class C has a plural method named “main” that takes any number of arguments. The possible outcome of evaluation is either a pair of a heap \( \mathcal {H} \) and a value v, a conceded exception, or undefined, where undefinedness is either due to divergence (non-termination of evaluation), or to a definition hole in the specification of ⇒, that is, of evaluation or execution (“stuckness”).
5.4 Implementation of a Num Interpreter in Prolog
The specification of Num in the preceding sections naturally lends itself to an implementation in Prolog. In fact, the Prolog implementation of Num and its formal specification evolved hand in hand, and the proof of Num’s safety is in close correspondence to a proof of the Prolog-based interpreter not failing on well-formed programs (see Section 5.5).
5.4.1 Parsing and Static Checking.
The parsing of Num programs using Prolog’s definite clause grammars (DCGs) requires a few modifications to the grammar of Figure 2, notably the spelling out of repeats (numbers of nonterminals) and the removal of the left recursion in the definition of expressions. As usual, the predicates of the DCG representing nonterminals are extended by an argument used to construct the abstract syntax tree, whose nodes are represented by Prolog terms and in which numbers of nodes are represented by lists. Using mapping over lists, translating the sanity, type, and number checks for Num programs specified in Sections 5.1 and 5.2 to Prolog rules is straightforward.
5.4.2 Interpretation.
The big-step relation ⇒ of Section 5.3 is naturally encoded by Prolog terms of the forms
\( \begin{equation*} \texttt {(P, H, L, e) => (Hp, V)}\quad \text{and}\quad \texttt {(P, H, L, s) => (Hp, Lp)} \end{equation*} \) where the program
The mapping of regular evaluation and execution rules to Prolog clauses is straightforward. For instance, the rule E-Count maps to the clause

where the syntactical pattern \( \texttt {|} e \texttt {|} \) from E-Count is represented by the term

where resolving the subgoal
5.4.3 Running Programs with the Prolog-Based Interpreter.
In accord with Section 5.3.7, Num programs are run in Prolog by resolving the goal
(1) | built-in propagation of exceptions as described above and | ||||
(2) | materialization of stuckness as a regular result, fail, and its automatic propagation to the top level. | ||||
The latter draws on Prolog’s closed-world assumption, i.e., on the fact that if Prolog cannot resolve a goal (viz. take an evaluation or execution step, or evaluate a side condition), it responds with failure rather than stuckness. This means that when given program P and expression e, the Prolog-based Num interpreter built from just the regular evaluation and execution rules complemented with the rules for throwing conceded exceptions will do one of four things: (1) succeed with
5.5 Safety of Num
The primary purpose of the number system of Num is to guarantee that (dynamic) counts are faithful to (static) numbers, that is, that all expressions having a countable (i.e., reference) type always evaluate to counts of objects that are covered by the numbers derived by Num’s number system for these expressions, and that all expressions having an uncountable (i.e., value) type evaluate to one value. For variables and fields this means that the count of the objects held by them is subsumed by their declared number. A different, yet equally important guarantee is that the number rules provided in Section 5.2 sufficiently guard the evaluation and execution rules of Section 5.3, meaning that they admit only programs that can be interpreted by these rules (so that interpretation does not get stuck). Of course, these guarantees are the exact analogs of the guarantees given by type safety, so that what we want is number safety. Given that Num’s number system has been crafted in analogy to its type system, and given that this type system is standard, it is immediately clear how number safety can be proved. The only hurdle to be taken is that for a proof based on big-step evaluation and execution rules, stuckness must be distinguished from divergence.
5.5.1 Stuckness as Failure.
As noted above, implementing the evaluation and execution rules as Prolog clauses implicitly provides for an explicit capture of being stuck: provided that rule selection is deterministic, under Prolog’s closed-world assumption, stuckness is equivalent to failure, except that the latter is a regular result that, similar to a thrown exception, is propagated to the start expression.10 By adding explicit failure rules [17, 18, 47] that share the shape of exception rules and hence can draw on exception propagation as introduced in Section 5.3.4, failure (as the result of running the Prolog-based interpreter) is reproduced in the inference rules that make the specification of dynamic semantics. For instance, complementing the rule

5.5.2 Type and Number Safety.
With stuckness captured as failure, we are equipped to state type and number safety of Num as follows:
For every well-typed, well-numbered, and otherwise well-formed program P and every start expression e that is well-typed and well-numbered with respect to P , if evaluation of ⟨ϵ, ϵ, e⟩ terminates, then we have ¬(⟨ϵ, ϵ, e⟩⇒ ↑fail), and if \( {\langle \epsilon , \epsilon , e\rangle } \Rightarrow \langle \mathcal {H}, v\rangle \), then \( \mathcal {H} \) and v are well-typed and well-numbered.
Note that, as usual, this phrasing of safety includes divergence and exceptional evaluation producing a conceded exception as safe cases. A complete capture of the safety theorem (including formal captures of well-typedness and well-numberedness of \( \mathcal {H} \) and v), together with its proof, will be given in Appendix B.
6 DISCUSSION
6.1 Countables and Uncountables
The separation into countables and uncountables may seem dogmatic, yet means that when dealing with primitive values (uncountables), one can largely ignore the number dimension, saving one from oddities such as branching on no boolean. If one rejects this high-level separation, one could have a slightly less dogmatic variant of Num, in which the types
That said, the separation of value types and reference types does not preclude us from using wrapper types (such as
6.2 Plurals vs. Containers
Numbers of objects, or containerless plurals, as featured by Num unify the handling of no, one, and many objects, without the drawbacks that would come with the use of containers. However, in situations in which numbers of objects have additional meaning attached, keeping them in containers is likely the better choice. This is typically the case when a collective noun is used to denote the plural: “sequence” implies not only that the objects that make the sequence are arranged in a specific order, but also that the particular order makes the sequence (not the objects!) different from others. Likewise, “priority queue” implies that the objects are sorted, and so on. In fact, each of these collective nouns gives rise to a special type of collection, which is also a data type, i.e., it does not only describe the structure in which the objects are contained, but also the operations that are defined on the structure (as opposed to those defined on the objects). Programs that build on these operations should use collections instead of numbers of objects.
And yet, there are also situations in which programmers would like numbers of objects to exhibit some properties that can be had with collections without buying into the data types and the reification that comes with them. For these cases, making the container type a parameter of the object type (which amounts to the inversion of the relationship of container and content [56, 59]) lets the programmer control the nature of the plural, that is, whether it is ordered, sorted, or has duplicates; and also its specific performance profile (e.g., linked list, array, or parallel access). To operate on a (hidden) container using its own protocol, it can be accessed through special syntax [59]; in all other contexts, the type of the number of objects is the type of its objects, as in Num.
Another disadvantage of the plurals of Num is that their implementation commits to one evaluation strategy for method invocations on numbers of objects, whereas when using containers and loops, other strategies can be programmed. While the built-in evaluation strategy will avoid significant complexity in some programs (by pushing it to the compiler), it may mean new complexity in others, namely, if they need a different evaluation strategy. For instance, singular method invocation on many objects (E-SingMApplC) prescribes that the actual parameters are (re-)evaluated for each receiver, which may not always be desired; to have a single evaluation instead, one would either need to cache the actuals in variables before the invocation or go through a plural method forwarding to the singular method. More advanced languages building on the ideas of Num should consider the use of adverbs, as advocated by Ungar and Adams [63] for the language Ly (cf. Section 7.2.6), to give programmers control over evaluation strategies.
6.3 Elimination of Type Cast Exceptions
While the introduction of number has eliminated null pointer exceptions, it could have also eliminated type cast exceptions, by redefining \( \texttt {(} C \texttt {)}\, e \) so that it returns all and only those objects among e that can be cast to C. For expressions e having number
6.4 Practicality, Utility, and Performance Penalty
In previous work [59], we refactored the core of the design-pattern rich JUnit 4 framework to using multitudes of objects (the forerunners of numbers of objects; cf. Section 7.1). The work showed that the refactoring is pervasive: apart from the obvious opportunities for code simplification (loop removal, removal of tests for not

in Java to

in Num exploits that the type of

with

additionally exploits that

from the adapter can be refactored to

a change that is reminiscent of rewriting a sentence from passive to active voice which, in Num, is generally possible because in Num, not only the object (parameter), but also the subject (receiver) of a predicate (method) can be a plural. In one particularly impressive example that additionally involves free conversion from (containerless) numbers of objects to lists (containers; see Section 6.2), the size of the code could be more than halved (see Steimann et al. [59] for the details). Overall, we found that with growing experience, more and more opportunities of using plurals for the better of the code became apparent.
Since we expected a performance penalty from the copying of many pointers that assignment means (where using containers only one pointer would need to be copied), we devised an alternative implementation of multitudes of objects that used lazy copying (similar to copy-on-write, but beware that Java’s copy-on-write collections serve a very different purpose). However, detailed performance measurements on the benchmarks used in the study [59] showed (1) that the performance degradation caused by eager copying was negligible when compared to the original, container-full design (less than 1% in three subjects and approximately 5% in one subject) and (2) that the introduction of lazy copying itself caused a notable performance overhead. Closer inspection showed that this was due to the fact that half of the collections copied in the subject programs were empty and less than 1% had sizes larger than 10, so that the cost of (eager) copying was lower than the cost of creating a lazy collection and going through the additional indirection that its use means.
6.5 Backward Compatibility
Not surprisingly, the case study [59] also showed that like the use of type annotations, the use of number annotations propagates through programs (including its used libraries and frameworks) and, therefore, that adopting numbers of objects in existing programs means a significant refactoring effort. To allow for a smooth transition to programming with numbers of objects, our earlier works [56, 57, 59] introduced a default number specifier, called bare, which expressed the same number constraint as
To avoid the challenges of dealing with optional primitive values obtained by accessing members having primitive types on receivers with static number
6.6 Application to Other Programming Paradigms
As noted in the introduction, the long-term goal of this work is making object-oriented programming more relational. Therefore, it has been embedded in an object-oriented, imperative setting. However, the discontinuities that come with the use of containers, specifically those induced by the indirection and the change in type that this means, also exist in functional languages. On the other hand, functional languages usually offer monadic types, including
7 RELATED WORK
7.1 Containerless Plurals
7.1.1 Bunch Theory.
Hehner [26], 27] developed a general theory of bunches of which the numbers of objects defined in Section 3.2 are a special case. Specifically, Hehner’s bunches are collections of elements that are conceived after sets, but are simpler in that they are always flat—while sets may comprise sets, bunches cannot be elements of bunches.12 Also, as for the numbers of objects on which Num builds, a bunch consisting of a single element, called an elementary bunch, and the element are indistinguishable. While the original definition of bunches [27] provided definitions of “element of” (∈) and “sub-bunch of” (⊆), in a reprise, Hehner [26] conflated ∈ and ⊆ for bunches into one inclusion relation (:) that corresponds to the definition of “among” (∝) in Num. Bunches do not distinguish between countables and uncountables; instead, the theory of bunches lifts arithmetic and other operators to bunches of values [26, 27] (see below for discussions of this practice).
Originally, Hehner [27, p. 26] suggested that bunches can be elements of sets (meaning that they “can be thought of as a one”). However, this contradicts the view of bunches as “the contents of a set” [26, pp. 14&17], because for a set containing bunches, the bunch given by the contents of this set would consist of bunches, and hence not be flat. By contrast, Num allows the implementation of collections of numbers of objects; for instance, in the example of Section 4.4, the number of the field
7.1.2 Strings.
Strings of words can also be considered containerless plurals, or “unpackaged sequences” [26, p. 17], but are more general than bunches since they allow duplicates. The same holds for the “unenclosed sequences” identified by Steele [55] as a recurring data structure of language specification, which uses various notations (a*, \( \overline{a} \), a1 … an), and indeed, instances of unenclosed sequences appear to be strings. It turns out that Hehner [27] also suggested to use bunches in language specification; that bunches do not allow duplicates is not a problem if the elements of such a bunch are distinct nodes of a syntax tree; in fact, the specification of Num in Section 5 used numbers of objects as “unenclosed sequences”.
7.1.3 Object-Oriented Programming with Multiplicities.
While this present work emphasizes containerlessness, my original ideas [56] and their prototype implementation in Java [59] rested on the inversion of the relationship between container and content: rather than making the element type a subscript to the container type (the parametric polymorphism of containers), the container type was made a subscript to the element type. For instance,
Native Multiplicities.
An alternative interpretation of my earlier captures of numbers of objects [56, 57, 59] can be found in the so-called native multiplicities of Harkes’s relations language devised for relational data modeling and querying [24, 25]. Native multiplicities follow this earlier work in that they separate type from multiplicity, but differ in that they extend multiplicity to primitive types. Harkes also provides type and number checking rules together with a safety proof for a sublanguage [25], which does, however, not have updatable stores. Also, native multiplicities lift all binary operations to the Cartesian product of their operands, which Hehner [27] also has for bunches. While forming the Cartesian product is in line with using bunches, or multi-values, for encoding nondeterminism (see Section 7.3), it has to be set off from array programming, which needs to consider pairwise application, and hence size checking (see Section 7.2.5).
7.2 Encoding Number in Type
7.2.1 Monads.
Functional languages like Haskell and F♯ come with special syntax for monads freeing the use of optional and list types from much of the notational clutter induced by the necessary wrapping and unwrapping. Yet, using monads for dealing with no or many objects is still different from code dealing with one (unwrapped) value. On the other hand,
7.2.2 Streams and Sequences.
The experimental languages Xen [37] and Cω [4], the forerunners of adding relational-style querying to object-oriented programming in C♯ [5], introduced streams as immutable, ordered, homogeneous collections (containers) of zero or more values (where values include primitive values, structs, and objects). Streams are constructed from base types using the same number specifiers as Num, and for a type T, the subtype axioms T! < : T, T < : T?, and T? < : T* provide for assignment compatibility (T! was dropped in Cω). The integration of T means that single objects (or values) and
Like the streams of Xen and Cω, the sequences of JavaFXTM [61] are flat, covariant containers, but unlike those streams, they have value semantics (they are copied upon assignment) and thereby afford mutability. As in Xen and Cω, single objects (or values) and
7.2.3 Optional Values.
C♯ has structs that, like primitive types, are value types (as opposed to the reference types of objects). The struct
In C♯, the operators defined for primitive types
7.2.4 Not Null and Safe Initialization.
While primitive types are usually not nullable by default, non-nullable reference types are only beginning to be seen in mainstream programming languages. In Kotlin,14 a Java dialect that is popular in the Android programming ecosystem, types are non-null by default; nullable types must be constructed from them, and are accompanied by a variant of member access, called safe call, that yields
Where static checking for non-nullness is available, it is usually considered an extended form of type checking, justified by regarding a non-nullable type as a subtype of the corresponding nullable type [60]. By contrast, Num separates number from type (but preserves, through subnumbering, that number
While static checking for not null, like static type checking, is generally limited by the possibilities of static analysis, one particulary unrelenting problem is that of initializing recursive, including circular, data structures [19, 51, 60]. Num’s approach to this problem is simplistic: It has no constructors and initializes all fields with literal values or new objects specified at the fields’ declaration site. Here, all
7.2.5 Array Programming.
The main goal of Num is preparing object-oriented programming for its development into object-relational programming. Indeed, that variables can hold, and expressions can evaluate to, flat and uncontained numbers of objects support the uniform navigation of relational and network data models (objects graphs) with arbitrary multiplicities (cf. Section 7.5). In other domains, however, in which many objects also play a central role, containers are essential carriers of meaning, and therefore must not be dismissed. For instance, computing with containers is the essence of array programming, as supported by languages such as APL [31]. In these languages, arrays are single values, each comprising a number of other values (which may be scalars or arrays). Operations defined on scalars are lifted to arrays in programmer-controllable ways (e.g., pairwise or cross product), resulting in very compact programs. Contemporary static typing allows capturing not only the dimension, but also the size of each dimension of an array in a (dependent) type [23, 53], thus making array programming not only type- and dimension-, but also size-safe. Even though size safety roughly corresponds to number safety as promoted by this work, I deliberately depart from array programming, for the simple reason that arrays are semantic, or meaning-carrying, containers that cannot be dropped: A vector for instance is a mathematical object, and as such not just a number of scalars (cf. the discussion in Section 6.2). Num caters for numbers of vectors (or containers), but again, this is to be distinguished from a vector of vectors, or a matrix, which are likewise single (singular) objects. Because containers are essential to array programming, this work, which promotes containerlessness, is not a competitor.
7.2.6 Ensembles and Adverbs.
The experimental language Ly aims at harnessing multi-core processors by organizing numbers of objects in ensembles [63]. An ensemble receiving a message delegates it to its members, which process it as specified by a so-called adverb (e.g., parallel, serial, or pairwise), unless the message (e.g.,
7.3 Nondeterminacy and Variation
Providing many objects where there would be expected only one can be used to express nondeterminacy [9, 54] or variation [16, 38] in programs. In fact, one use of bunches suggested by Hehner [26, p. 89] and also by Morris and Bunkenburg [41] is the representation of nondeterminacy: A bunch can present a choice of possible values, just like a probability distribution (random variable) or a fuzzy set can. The numbers of objects presented by this present work could also be interpreted in this way; yet, as noted in the introduction, my goal is to make object-oriented programming more relational (but see the discussion of Prolog below for how this can be viewed as two sides of the same coin).
Generally, deterministic programming languages can be made nondeterministic by introduction of a choice operator [9, 54], which expresses the (nondeterministic) selection from a number of values. Embedding such an operator into a language gives rise to several non-trivial design decisions; however, the most basic perhaps being whether in computations, “a variable is always bound to exactly one value or is bound to a set of possible values” [54, p. 518]. That this choice is essential can be seen from the simple example of computing the value of x + x where the value of x is nondeterministically chosen from the set {1, 2}: If x is always bound to exactly one value (dubbed “singular semantics” by Søndergaard and Sestoft [54]), x + x evaluates to 2 or 4, whereas if x is bound to the set {1, 2} (“plural semantics”), x + x evaluates to {2, 3, 4}. There are various ways of reducing the combinatorial complexity introduced by plural semantics and the need to form the Cartesian product of choices, including choice elimination [16], narrowing [9], and the introduction of choice dimensions [16] or variability contexts [38]. This present work, although adopting plural semantics, is careful to not introduce Cartesian products (even though Num can compute them if needed; see Section 4.3).
Singular semantics is adopted by (sequential) Prolog, which uses backtracking to go through all possible choices (bindings of variables). However, nondeterminacy is only one interpretation of Prolog programs—the other is that Prolog is relational, i.e., it computes relations rather than functions [64]. This present work is also concerned with implementing relations: specifically, an object’s field holding many objects implements a relationship between the field owner and the objects held by the field. This relationship is simultaneous and between all objects; specifically, it is not meant as a choice. If it is interpreted as one nevertheless, one needs to be clear about what the absence of an object (“no object”) means, specifically if the language does not have a notion of failure as a regular result (as Prolog does).
7.4 First Class Relationships
Until this day, relationships are typically implemented in object-oriented programming languages by using coding patterns [43], which include the use of fields for directed N:1 relationships. This is despite the fact that very early in the rise of object-oriented programming, Rumbaugh [48] already argued, with good reasons, for the lifting of relationship encodings to the level of a first class language construct. For this purpose, he introduced relations as instances of a special class
Bierman and Wren’s RelJ [8] is based on a notion of relationships as first class types whose instances, called relationship instances, are tuples. These tuples, which—like objects—can have state and behavior, are created and returned by adding a pair of objects to a relationship. Navigation of a relationship always results in a set; since sets have value semantics, the result type of navigation is covariant with the target type of the navigation. However, member, including relationship, access is not lifted to sets; specifically, sets cannot be the source of navigation, so that navigation cannot be chained as in Num. Bierman and Wren also suggest how multiplicities could be restricted statically, using one and many annotations (analogous to Num’s
In the language Rumer, references to objects are completely expelled from so-called entity types (conventional classes), and objects are related exclusively through relationship types [3]. It follows that only relationships know which entities are related (referred to as stratification in [3]). Entity and relationship types have associated extent types, which must be instantiated and populated explicitly. Relationships can be nested, and relationship extents can be owned by relationships, so that they cannot escape the owning relationship. While owned relationship extents bear some resemblance to numbers of objects (which likewise cannot be aliased), Rumer’s approach seems rather heavy weight—in particular, with all knowledge about relationships fully encapsulated in relationships (so that objects are ignorant of whether an how they are related), much of an application’s logic (including that captured in most methods) has to be moved to relationships, with objects being degraded mostly to passive data containers with identity. This means a fundamental paradigm shift for object-oriented programming, where Num advocates a more lightweight approach, in which fields implement one direction of a binary relation.
7.5 Multiplicities in Modeling and Query Languages
In modeling languages such as the Unified Modeling Language (UML) [45], the Object Constraint Language (OCL) [12, 46], or Alloy [32], number constraints are expressed as multiplicities. While multiplicities can be arbitrary sets of natural numbers, the multiplicities [0, 1], [1, 1], and [0, ∞) (corresponding to
OCL [12, 46], which is used to express well-formedness conditions of UML models, allows the navigation (dereferencing) of attributes and associations independently of their multiplicity, using the same dot operator. However, OCL contains numbers of objects in collections; the difference between one and many objects is conjured away by allowing collection operations to be applied to single objects also (meaning that they are coerced to singleton containers). Extra wrapping is not necessary in Alloy [32], which does not distinguish between scalars and sets, but instead represents scalars as singleton sets (so that the difference between ⊆ and ∈ disappears, leaving only ⊆). Except for the use of sets as containers, this is close to the numbers of objects of this present work, and indeed, Alloy is relational; yet, it is not a programming language.
While Alloy and UML are relational, other data, XML- and DOM-based in particular, is tree-structured. Navigation in trees is supported by powerful query languages such as jQuery, which is implemented as a JavaScript library offering a fluent query API. As in OCL, Num and some other languages, in jQuery, no, one, and many objects are navigated using the same expressions, which means that, as discussed in Section 4.1 for Num, queries may “fail silently”: navigation through partial expressions resulting in “no object” lead to no object, and executing a method on no object does nothing (rather than flag an error). To address this, Lerner et al. [34] have devised a static type system for jQuery programs in which so-called multiplicities annotate container types with abstract size information
8 CONCLUSION
For most programs, occurrences of no or many objects are not exceptional, but standard situations. Rather than encoding these standard situations using special constructs, such as null pointers, collections, or monads, I have advocated a general paradigm shift: moving on from single objects, or singulars, to any number of objects, or plurals. While the generalization of one to many in programming languages has been proposed before, it has relied on the use of containers, and with it on the type system to encode the differences between singular and plural. By contrast, my work introduces number as a largely orthogonal dimension in language design that avoids the quirks of type-based solutions; its very own quirk, that it separates values into countables (objects) and uncountables (non-objects), may be seen as the generalization of a distinction that is already widely established, namely, that between reference types (which may be viewed as expressing limited countability, with counts 0 and 1) and value types (which require wrapping to express the absence of a value). For reference types, the introduction of numbers of objects means the elimination of null pointer dereferences, but not as the result of a special effort, but as part of a general solution that eliminates the use of containers for representing many objects also, thus making none, one, and many degrees on one scale. Number safety guarantees that this new scale is not a source of failure.
APPENDICES
A SEMANTICS OF NUM
A.1 Type Rules
A.1.1 Type Rules for Expressions.


A.1.2 Type Rules for Statements.

A.1.3 Type Rules for Definitions.


A.2 Number Rules
A.2.1 Number Rules for Expressions.

A.2.2 Number Rules for Statements.

A.2.3 Number Rules for Definitions.

A.3 Dynamic Semantics
I distinguish regular and exceptional evaluation of expressions and execution of statements. For the result of exceptional evaluation and execution, I further distinguish between conceded exceptions and failure, where the latter reifies definition holes (“stuckness”).
A.3.1 Regular Evaluation of Expressions.
Regular evaluation steps have the form \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, v\rangle \).


If for some \( \mathcal {H} \), \( \mathcal {L} \), and e, \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, v\rangle \), I will say that evaluation of \( \langle \mathcal {H}, \mathcal {L}, e\rangle \) succeeds.
A.3.2 Exceptional Evaluation of Expressions.
Exceptional evaluation steps have the form \( \langle \mathcal {H}, \mathcal {L}^{\prime }, e\rangle \Rightarrow \; \uparrow \!\! \mathit {X} \), where X is either a conceded exception (introduced here), or fail (introduced in Appendix A.3.5), or exhausted (introduced in Appendix B.5).

A.3.3 Regular Execution of Statements.
Regular execution steps have the form \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow \langle \mathcal {H}^{\prime },\mathcal {L}^{\prime } \rangle \).

A.3.4 Exception Propagation Rules.
Exception propagation rules define exceptional evaluation and execution steps conditioned on exceptional evaluation or execution substeps. Their definition is generic: for each rule of A.3.1–A.3.3 having shape


A.3.5 Failure Rules.
Failure rules define exceptional evaluation and execution steps where the exception is not a conceded exception, but fail (which reifies stuckness, by turning it into an unconceded exception). Their definition is also generic: for each rule


For rules that need disambiguation for their selection, and where the disambiguating side conditions are not exhaustive (leaving cases in which evaluation may get stuck), we need specific failure rules. Specifically, in the case of E-IfT and E-IfF, we need the rule


B PROOF OF TYPE AND NUMBER SAFETY
The safety proof follows a standard scheme. Its main contribution is showing that the static numbering of member access, object addition, and object subtraction correctly abstract from the counts of objects seen at runtime, and that the evaluation and execution rules provided cover all well-numbered programs. That this requires detailed case analyses may be taken as indication that, as intended, the accidental complexity of dealing with none, one, and many has been shifted from programs to the implementation of the language.
First, we need to provide definitions of well-typedness and well-numberedness for values and heaps (which were not provided in Section 5) as well as for locals stores.
B.1 Typing and Numbering of Values
The connection between values v (as defined in Section 5.3) and their abstractions type and number, which has remained implicit so far, is established by the relations \( \mathcal {H}\vdash v : \tau \) and \( \vdash v {\#}\eta \), defined by the rules

Together with the definition of the subnumer relation \( \lt \!{\#} \) in Table 1(a), N-Objs establishes the connection between (static) number and (dynamic) count, which has so far only informally been introduced (in Section 4). Specifically, for \( \vdash {l}_1\, \ldots \, {l}_{n}{\#}\eta \) we have that n ∈ [0, 0] if \( \eta =\texttt {-} \), n ∈ [1, 1] if \( \eta =\texttt {!} \), n ∈ [0, 1] if \( \eta =\texttt {?} \), and n ∈ [0, ∞) if \( \eta =\texttt {*} \).
B.2 Well-formedness of Heap and Locals Store
Next, we need to define well-formedness of evaluation and execution contexts. For heaps, well-formedness is defined by



B.3 Derived Type and Number Environments
Following the examples of Bierman et al. [4], 7], the locals store \( \mathcal {L} \) carries the typing and numbering environments \( \mathcal {T} \) and \( \mathcal {N} \) used by the type and number rules of Section 5.2, which will be required by the safety proofs. These environments are extracted from \( \mathcal {L} \) using the two definitions

B.4 Useful Lemmas
Because the sub-steps and side conditions of a step may alter the heap and the locals store, it is convenient to have the following three lemmas:
If \( \mathcal {H}\checkmark \), \( \mathcal {H}\vdash \mathcal {L}\checkmark \), and \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, {l}_1\, \ldots \, {l}_{n}\rangle \), then \( {l}_1\, \ldots \, {l}_{n} \propto dom(\mathcal {H}^{\prime }) \).
If expressions evaluate to locations, then these locations exist on the heap.
Simultaneous induction on the rules for evaluation and execution. Locations l are either new or taken from \( \mathcal {H} \) (as values of fields) or taken from \( \mathcal {L} \) (as values of variables). New locations are exclusively introduced through E-New, which adds them to the heap. \( \mathcal {H}\checkmark \) and \( \mathcal {H}\vdash \mathcal {L}\checkmark \) imply that for all locations l stored in \( \mathcal {H} \) or \( \mathcal {L} \), \( l \propto dom(\mathcal {H}) \).□
If \( \mathcal {H}\vdash v:\tau \) and \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, v^{\prime }\rangle \) or \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow \langle \mathcal {H}^{\prime },\mathcal {L}^{\prime } \rangle \), then \( \mathcal {H}^{\prime } \vdash v:\tau \).
The type of a value does not change if the heap is updated.
Using T-Obs, T-Bool, and T-Int, simultaneous induction on the evaluation and execution rules. The only rules that change the heap are E-New and E-FldAssign, which neither remove a location nor update fχ.□
If \( \mathcal {H}\vdash \mathcal {L}\checkmark \) and \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, v^{\prime }\rangle \) or \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow \langle \mathcal {H}^{\prime },\mathcal {L}^{\prime } \rangle \), then \( \mathcal {H}^{\prime } \vdash \mathcal {L}\checkmark \).
Heap update does not affect well-formedness of the locals store.
This follows immediately from W-Locals and Lemma 2.□
B.5 Main Theorem
Having to work with big-step operational semantics, the proof of type and number safety needs to take the possibility of divergence into account, for which the relation ⇒ is undefined (or, equivalently, for which no finite derivations exist). It does so by using the device of a non-negative counter c [18, 52] (also called fuel [1]), which makes sure that evaluations of expressions and executions of statements always terminate, if only with the result that the counter has been exhausted before regular termination is reached. This is sufficient, since for terminating programs, an initialization of the counter that will not be exhausted can always be found, and non-terminating programs (which exhaust every counter) do not compromise safety.
Following Ernst et al. [18], I define a finite evaluation and execution relation ⇒c by duplicating the evaluation and execution rules of Appendix A.3.1–A.3.5, where each occurrence of ⇒ in the premise of a duplicated rule is replaced with ⇒c, and where ⇒ in the conclusion of a duplicated rule is replaced with ⇒c + 1.15 To the so obtained, counter-based rule set, I add the exhaustion axioms \( \begin{equation*} \langle \mathcal {H}, \mathcal {L}, e\rangle \Rightarrow _{0}\; \uparrow \!\! \mathit {\mathit {exhausted}} \quad \text{and}\quad \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow _{0}\; \uparrow \!\! \mathit {\mathit {exhausted}} \,, \end{equation*} \) which limit the depth of the derivation trees of ⇒d to d. Here, exhausted is a (conceded) exception that is propagated using the standard exception propagation rules, so that ⇒d returns exhausted if d is too small for the full derivation (which may be infinite).
Given that the failure rules of Appendix A.3.5 cover all cases left by the regular and exceptional evaluation rules of A.3.1–A.3.4, we have that for all d, \( \mathcal {H} \), \( \mathcal {L} \), and e, we get either \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow _{d} \langle \mathcal {H}^{\prime }, v\rangle \) or \( \langle \mathcal {H}, \mathcal {L}, e\rangle \Rightarrow _{d}\; \uparrow \!\! \mathit {X} \), and for all d, \( \mathcal {H} \), \( \mathcal {L} \), and s1 … sn, we get either \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow _{d} \langle \mathcal {H}^{\prime },\mathcal {L}^{\prime } \rangle \) or \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow _{d}\; \uparrow \!\! \mathit {X} \), where X ∈ {TypeCastException, NumberCastException, fail, exhausted}. This allows us to prove the following two, mutually dependent lemmas, one for the evaluation of expressions and one for the execution of statements.16
For expressions e, heaps \( \mathcal {H} \), and locals stores \( \mathcal {L} \) such that

and for all d ≥ 0,
(a) \( \lnot \big (\langle \mathcal {H}, \mathcal {L}, e\rangle \Rightarrow _{d}\; \uparrow \!\! \mathit {\mathit {fail}} \,\big) \)
(b) if \( {\langle \mathcal {H}, \mathcal {L}, e\rangle } \Rightarrow _{d} \langle \mathcal {H}^{\prime }, v\rangle \), then

The proof is by induction on d. For all e, \( \langle \mathcal {H}, \mathcal {L}, e\rangle \Rightarrow _{0}\; \uparrow \!\! \mathit {\mathit {exhausted}} \) by the exhaustion axioms, so that for d = 0, (a) trivially holds and (b) is vacuously true. For d > 0, the proof is structured by case analysis on the syntactic forms of expressions, relying on Lemma 5 in the case of method application (where evaluation involves the execution of the method body). In the proof, I will refer to assumptions (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) through (\( \color{#4863A0}{e\checkmark \!\!_\eta :} \)) jointly as the precondition of an evaluation step, and to (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) through (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) as its postcondition. Also, to the counter-based duplicate of a rule named E-Name I will refer as E-Namec. Recall that for ease of expression, for \( \lnot \big (\langle \mathcal {H}, \mathcal {L}, \_\rangle \Rightarrow \; \uparrow \!\! \mathit {\mathit {fail}} \,\big) \) I will sometimes say that evaluation or execution does not fail, and for \( {\langle \mathcal {H}, \mathcal {L}, \_\rangle } \Rightarrow \langle \mathcal {H}^{\prime }, \_\rangle , \) I will say that evaluation or execution succeeds. Likewise, for side conditions that are satisfied, I will say that they succeed; otherwise, I will say that they fail (which includes the case that they are undefined on given arguments). \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {true}} \end{equation*} \)
(a) By use of E-Truec, evaluation trivially succeeds and therefore does not fail.
(b) Since \( \mathcal {H}^{\prime }=\mathcal {H} \), (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) follow directly from (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) and (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)); (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) follows directly from (\( \color{#4863A0}{e\checkmark \!\!_\tau :} \)), T-True, and T-Bool; (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) follows accordingly.
\( \begin{equation*} \boxed{\vphantom{a^C}\texttt {false}}\quad \boxed{\vphantom{a^C}i}\quad \boxed{\vphantom{a^C}\texttt {no}~ C} \end{equation*} \) (a) and (b) are analogous to the case of
(a) Given the well-formedness of P, the first side condition of E-NewObjc does not fail and we may assume (\( \color{#4863A0}{e\checkmark \!\!_\tau :} \)) and (\( \color{#4863A0}{e\checkmark \!\!_\eta :} \)) for all iei. Given (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) and (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)) from the precondition of the evaluation step, we may assume by the induction hypothesis that the first subevaluation in the sequence from i = 1..n does not fail and if it succeeds, the postcondition holds for \( \mathcal {H}_1 \), \( \mathcal {L} \), and v1. This implies the precondition of the next subevaluation in the sequence and so on for all remaining subevaluations. A new location l can always be chosen and the final side condition succeeds (and hence does not fail) because \( l\not\propto dom(\mathcal {H}_n) \). Hence, evaluation of
(b) If the evaluation succeeds, its subevaluations must have succeeded, so that by the induction hypothesis, part (b), and Lemma 2, the postcondition holds for \( \mathcal {H}_n \), \( \mathcal {L} \), and all vi. We still need to show that it also holds for \( \mathcal {H}^{\prime } \). We do this separately for each part of the postcondition. (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)): Because l is new and because we know that (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) already holds for \( \mathcal {H}_n \), we must only show W-Obj for the added l. With fχ = C, the fields f1 … fn used in E-NewObjc and W-Obj are identical; that the fields’ values v1 … vn assigned by E-NewObjc satisfy W-Obj follows from T-Prog, N-Prog, T-Class, N-Class, T-FldDef, and N-FldDef, in conjunction with Lemma 2 and (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) for each vi as already shown above. (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)): This follows directly from Lemma 3 and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) for \( \mathcal {H}_n \) as shown above. (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)): follows from T-NewObj (which requires the new object to have type C), the construction of the new object in E-NewObjc (which sets fχ to C), and T-Objs. (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) is immediate, since \( \vdash l{\#}\texttt {!} \), as required by N-NewObj. \( \begin{equation*} \boxed{\vphantom{a^c}x} \end{equation*} \)
(a) Given (\( \color{#4863A0}{e\checkmark \!\!_\tau :} \)), (\( \color{#4863A0}{e\checkmark \!\!_\eta :} \)), T-Var, and N-Var, we know that \( x\propto dom(\mathcal {T}(\mathcal {L})) \) and \( x\propto dom(\mathcal {N}(\mathcal {L})) \) and hence from the definition of \( \mathcal {T}(\mathcal {L}) \) and \( \mathcal {N}(\mathcal {L}) \) that \( x\propto dom(\mathcal {L}) \), so that the side condition \( \mathcal {L}[x]=\rho \,v \) of, and with it evaluation through, E-Varc succeeds, and therefore does not fail.
(b) \( \mathcal {H}^{\prime }=\mathcal {H} \) so that (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) trivially hold; (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) trivially follow from (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)). \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {this}} \end{equation*} \) (a) Given (\( \color{#4863A0}{e\checkmark \!\!_\tau :} \)), (\( \color{#4863A0}{e\checkmark \!\!_\eta :} \)), T-This, and N-This we can infer, in analogy to variable access, that \( \mathit {rec}\propto dom(\mathcal {L}) \). Given (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)) and N-This, we know that \( \mathcal {L}[rec] \) is a single location, so that the side condition and with it evaluation through E-Thisc succeeds, and therefore does not fail.
(b) analogous to the case of x above
\( \begin{equation*} \boxed{\vphantom{a^c}\texttt {these}} \end{equation*} \) (a) and (b) analogous, with
(a) Evaluation is through E-Fieldc. For the first side condition, see the case of
(b) Because \( \mathcal {H}^{\prime }=\mathcal {H} \), (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) from the postcondition trivially hold. By (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and W-Heap, we have that \( \mathcal {H}\vdash v:\mathit {typ}(\!\mathit {rng}(C.f)) \) and \( \vdash v{\#}\mathit {num}(\!\mathit {rng}(C.f)) \), which is what is prescribed by T-Field and N-Field, hence proving (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)). \( \begin{equation*} \boxed{\vphantom{a^c}{e_0}^{C}\!\!\texttt {.}m\texttt {(}e_1\texttt {,}\ldots \texttt {,}e_p\texttt {)}} \end{equation*} \)
The selection of the regular evaluation rule for method application, either E-SingMApplUc, E-SingMApplCc, or E-PurMApplc, is governed by the initial side conditions of each rule which, as is easily checked, are mutually exclusive (so that selection is deterministic). Because for sig(C.m) = ρ ρ1 … ρp↦ρ′, N-MethInvoc and N-MAppl guarantee that \( \rho =\texttt {!}\vee \rho =\texttt {*} \), and because the well-definedness of num(ρ′) is guaranteed by the syntax of Num, precisely one rule of the rules (and not the failure rule; cf. Appendix A.3.5) is always selected. I distinguish by the different rules.
E-SingMApplUc.
(a) That the third side condition does not fail for well-formed programs is easily checked. By the precondition of the evaluation step, the well-formedness rules T-MAppl and N-MAppl, and the induction hypothesis, part (a), the first subevaluation (determining the receiver) does not fail. If it succeeds, we know from the induction hypothesis, part (b), N-MAppl with \( \eta ^{\prime }_0=\texttt {!} \) and η′ = ηϵ (the conditions for this case, where η′ = num(ρ) via congruence in N-MAppl and E-SingMApplUc), and from Table 1(b) that \( \eta _0=\texttt {!} \) and therefore \( \mathcal {N}(\mathcal {L})\vdash e_0{\#}\texttt {!} \) and (via (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) from the induction hypothesis) that \( \vdash v_0{\#}\texttt {!} \), satisfying the fourth side condition v0 = l. Together with T-MAppl and N-MAppl, the postcondition of the first substep (for \( \mathcal {H}_0 \) and \( \mathcal {L} \)) implies the precondition of the second (evaluation of the first parameter). Repeatedly applying the same reasoning as for the evaluation of the receiver to all parameter evaluations means that evaluation of parameters does not fail and if it succeeds, we know by the induction hypothesis, part (b), and Lemma 2 that the postcondition holds for \( \mathcal {H}_p \), \( \mathcal {L} \), and all vi. The next two side conditions do not fail because of Lemma 1, (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) for \( \mathcal {H}_0 \), and the well-formedness of the program. The construction of \( \mathcal {L}^{\prime } \) cannot fail; given that as shown, (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) hold for all vi (and hence also for l), and \( \_ \) stands for an arbitrary suitable value, (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)) holds for \( \mathcal {L}^{\prime } \) by its construction. That the last substep, execution of the method body, does not fail follows from the induction hypothesis in conjunction with Lemma 5 and the satisfaction of its preconditions (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)), (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)), (\( \color{#4863A0}{s\checkmark \!\!_\tau :} \)), and (\( \color{#4863A0}{s\checkmark \!\!_\eta :} \)) applied to \( \mathcal {H}_p \), \( \mathcal {L}^{\prime } \), and s1 … sb, where the first two are granted by the above and the latter two, instantiated to \( \mathcal {T}(\mathcal {L}^{\prime })\vdash {s}_1\, \ldots \, {s}_{b}\checkmark \!\!_\tau \) and \( \mathcal {N}(\mathcal {L}^{\prime })\vdash {s}_1\, \ldots \, {s}_{b}\checkmark \!\!_\eta \), are satisfied given the well-formedness of methods (implied by the well-formedness of P), and the congruence of \( \mathcal {T}(\mathcal {L}^{\prime }) \) and \( \mathcal {N}(\mathcal {L}^{\prime }) \) with the type and number environments in T-MethDef and N-MethDef. If the execution of statements succeeds, the final side condition also succeeds because updates of \( \mathcal {L}^{\prime } \) cannot remove ret or alter its range ρ.
(b) If the evaluation succeeds, its subevaluations must have succeeded, so that we get (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) for \( \mathcal {H}^{\prime } \) and \( \mathcal {L}^{\prime \prime } \), as well as (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \), from Lemmas 5 and 3. From \( \mathcal {H}^{\prime }\vdash \mathcal {L}^{\prime \prime }\checkmark \) together with the construction of \( \mathcal {L}^{\prime } \) and hence also \( \mathcal {L}^{\prime \prime } \), we can infer \( \mathcal {H}^{\prime }\vdash v:\mathit {typ}(\rho) \) and \( \vdash v{\#}\mathit {num}(\rho) \), where ρ is the return range of C.m. This, however, is precisely what is required by T-MAppl and N-MAppl (where, as shown in part (a), \( \eta _0=\texttt {!} \) so that η = η′), meaning that (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) hold for v, the value of ret in \( \mathcal {L}^{\prime \prime } \) to be returned by m.
E-SingMApplCc.
(a) The proof proceeds in analogy to that for E-SingMApplUc, the main differences being that e0 may now evaluate to an arbitrary count of objects (requiring repeated evaluations of the parameters and execution of the method body) and that the non-failure of the final object addition must be proved, which is however trivial, since \( \mathcal {L}_i^{\prime }[\mathit {ret}]=\rho \,v_i \) and num(ρ) ≠ ηϵ mean that all vi are numbers of objects (note that ⊕ has no other type constraints).
(b) The proofs of (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \) proceed in analogy to the case of E-SingMApplUc. To prove (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) for v, we must show that given (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) for each \( \mathcal {H}_{i(p+1)} \) and vi, \( \mathcal {H}^{\prime }\vdash (v_1\oplus \ldots \oplus v_n) : \tau \), where τ = typ(ρ) is the return type of the invoked method (and all its overridings). This is however immediate from (i) the fact that for each vi, \( \mathcal {H}^{\prime }\vdash v_i : \tau \) follows from (\( {:v\checkmark \!\!_\tau } \)) (meaning \( \mathcal {H}_{i(p+1)}\vdash v_i : \tau \)), and Lemma 2, (ii) the definition of object addition in Section 3.2, and (iii) T-Objs. To prove (\( {:v\checkmark \!\!_\eta } \)) for v, we must show that given (\( {:v\checkmark \!\!_\eta } \)) for each vi, \( \vdash (v_1\oplus \ldots \oplus v_n) {\#}\eta \), where η = η0.η′ as defined in Table 1(b), η0 is the number of the receiver expression e0, and η′ is the return number of the invoked method in N-MAppl and num(ρ) in E-SingMApplCc. To show that \( \vdash v{\#}\eta \), we proceed by case analysis on η0 and η′, reading of η = η0.η′ from Table 1(b).
— | If \( \eta _0=\texttt {-} \), then \( \eta =\texttt {-} \). Using \( \mathcal {N}(\mathcal {L}) \vdash e_0{\#}\texttt {-} \) and the induction hypothesis, r = 0, so that v = ϵ and, by N-Objs, \( \vdash v {\#}\texttt {-} \). | ||||
— | If \( \eta _0=\texttt {!} \), then η = η′. Based on the same reasoning, r = 1, so that v = v1, where we already know that \( \vdash v_1{\#}\eta ^{\prime } \). | ||||
— | If \( \eta _0=\texttt {?} \) and \( \eta ^{\prime }\ne \texttt {!} \), then η = η′. If r = 0, v = ϵ and \( \vdash v{\#}\eta ^{\prime } \) by N-Objs; if r = 1, \( \vdash v{\#}\eta ^{\prime } \) for the same reasons as for the \( \eta _0=\texttt {!} \) case. | ||||
— | If \( \eta _0=\texttt {?} \) and \( \eta ^{\prime }=\texttt {!} \), then \( \eta =\texttt {?} \). If r = 0, v = ϵ and \( \vdash v{\#}\texttt {?} \) by N-Objs; if r = 1, v = v1, where we already know that \( \vdash v_1{\#}\texttt {!} \), so that \( \vdash v{\#}\texttt {?} \) (by N-Objs). | ||||
— | If \( \eta _0=\texttt {*} \) and \( \eta ^{\prime }=\texttt {-} \), then \( \eta =\texttt {-} \). Since we know that in this case, \( \vdash v_i{\#}\texttt {-} \) for all vi, we also know that v = ϵ and hence \( \vdash v{\#}\texttt {-} \). | ||||
— | If \( \eta _0=\texttt {*} \) and \( \eta ^{\prime }\ne \texttt {-} \), then \( \eta =\texttt {*} \). \( \vdash v{\#}\texttt {*} \) trivially holds by N-Objs and the definition of \( \lt \!{\#} \) in Table 1(a). | ||||
E-PlurMApplc.
(a) The proof proceeds in analogy to that for E-SingMApplUc, with the relaxation that e0 is not required to evaluate to a single location.
(b) The proof of (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) is analogous to the E-SingMApplc case. Since in N-MApplc, \( \eta _0^{\prime }=\texttt {*} \) as per this case, we get η = η′ as for the the E-SingMApplUc case, so that the proof of (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) is also analogous. \( \begin{equation*} \boxed{\vphantom{a^c}e_1\;\texttt {+}^\kappa \; e_2}\quad \boxed{\vphantom{a^c}e_1\;\texttt {-}^\kappa \; e_2} \end{equation*} \) The applicable rule, E-BinOpc, covers addition and subtraction for integers and objects. The proofs for addition and subtraction differ slightly; I begin with the former, and for the latter show only the differences.
Addition.
(a) The first two side conditions do not fail because for this case, ± = + and because from (\( \color{#4863A0}{e\checkmark \!\!_\tau :} \)) and the well-formedness rules T-IntAdd and T-ObjAdd, we know that κ is either int or obj. By the precondition of the evaluation step, T-IntAdd, T-ObjAdd, N-Add, and the induction hypothesis, part (a), we may assume that the first subevaluation of E-BinOpc does not fail. If the first subevaluation succeeds, by the induction hypothesis, part (b), we may assume that its postcondition holds for \( \mathcal {H}^{\prime \prime } \), \( \mathcal {L} \), and v1, which is sufficient to assume that the second subevaluation does not fail either (by the same reasoning as for the first). Again, if it succeeds, we may assume by the induction hypothesis, part (b), and Lemma 2 that: for κ = int, \( \mathcal {H}^{\prime }\vdash v_1:\mathit {int} \) and \( \mathcal {H}^{\prime }\vdash v_2:\mathit {int} \), so that v1 + v2 is well-defined and the side condition succeeds; for κ = obj, \( \mathcal {H}^{\prime }\vdash v_1:C_1 \) and \( \mathcal {H}^{\prime }\vdash v_2:C_2 \) , so that v1⊕v2 is well-defined and the side condition succeeds, so that (a) follows.
(b) If the evaluation succeeds, we know that both subevaluations must have succeeded, so that by the induction hypothesis, part (b), (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) hold for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \). For κ = int, v has been computed by integer addition so that \( \mathcal {H}^{\prime }\vdash v:\mathit {int} \) and \( \vdash v{\#}\eta _\epsilon \) as required by (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)); for κ = obj, proving (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) requires more detailed analyses.
For (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)), we need to show that for v = v1⊕v2, \( \mathcal {H}^{\prime }\vdash v:\mathit {lcs}(C_1,C_2) \), where we can assume \( \mathcal {H}^{\prime }\vdash v_1:C_1 \) and \( \mathcal {H}^{\prime }\vdash v_2:C_2 \) by the induction hypothesis. From the definition of lcs in Section 5.2.1, we have that C1 < : lcs(C1, C2) and C2 < : lcs(C1, C2), and hence by T-Objs that \( \mathcal {H}^{\prime }\vdash v_1:\mathit {lcs}(C_1,C_2) \) and \( \mathcal {H}^{\prime }\vdash v_2:\mathit {lcs}(C_1,C_2) \), and furthermore that \( \mathcal {H}^{\prime }\vdash l:\mathit {lcs}(C_1,C_2) \) for each l∝v1 and each l∝v2. (\( {:v\checkmark \!\!_\tau } \)) thus follows from T-Objs and the definition of ⊕ in Section 3.2.
For (\( {:v\checkmark \!\!_\eta } \)), we need to show that ⊢v: η where η = η1 + η2 according to Table 1(c) and where we can assume \( \vdash v_1{\#}\eta _1 \) and \( \vdash v_2{\#}\eta _2 \) by the induction hypothesis. We proceed by a case analysis based on Table 1(c), which gives us η for each combination of η1 and η2.
— | All cases yielding \( \eta =\texttt {*} \) are trivial (\( \vdash v{\#}\texttt {*} \) always holds by N-Objs). | ||||
— | If \( \eta _1=\texttt {-} \), then η = η2. Since we know by N-Objs that in this case, v1 = ϵ, from the definition of ⊕ in Section 3.2 we get v = v1⊕v2 = v2 and therefore, given \( \vdash v_2{\#}\eta _2 \), \( \vdash v{\#}\eta \), as required. | ||||
— | |||||
This already covers all cases of object addition; the case where η1 = η2 = ηϵ is handled by integer addition and mixing countable with uncountable operands (the undefined cases in Table 1(c) is excluded by typing.
Subtraction.
(a) Analogous to the addition case.
(b) The proofs of (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) are analogous to the addition case, as are the proofs of (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) for κ = int; those for κ = obj must be adapted to the specifics of object subtraction.
For (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)), we need to show that for v = v1⊖v2, \( \mathcal {H}^{\prime }\vdash v:C_1 \), where we can assume \( \mathcal {H}^{\prime }\vdash v_1:C_1 \) by the induction hypothesis. Since v1⊖v2 can only yield objects that are among v1 (see its definition in Section 3.2), \( \mathcal {H}^{\prime }\vdash v:C_1 \) follows from \( \mathcal {H}^{\prime }\vdash v_1:C_1 \) and T-Objs.
For (\( {:v\checkmark \!\!_\eta } \)), we need to show that ⊢v: η where η = η1 − η2 according to Table 1(d) and where we can assume \( \vdash v_1{\#}\eta _1 \) and \( \vdash v_2{\#}\eta _2 \) by the induction hypothesis. Since we know from the definition of ⊖ in Section 3.2 that |v1⊖v2| ≤ |v1|, (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) trivially holds for all cases in which, according to Table 1(d), η1 − η2 = η1. For the remaining cases, we have that \( \eta _1=\texttt {!} \) and \( \eta =\texttt {?} \) and, hence, that |v1| = 1; according to the definition of object subtraction in Section 3.2, subtracting from one object can only yield no or one object, which is covered by \( \eta =\texttt {?} \), so that (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) follows in all cases. \( \begin{equation*} \boxed{\vphantom{a^c}e_1\;\texttt {==}\;e_2} \end{equation*} \)
(a) By the precondition of the evaluation step, the well-formedness rules T-EqId and N-EqId, and the induction hypothesis, part (a), we may assume that the first subevaluation of the applicable evaluation rule, E-EqIdc, does not fail. If it succeeds, by the induction hypothesis, part (b), we may assume that its postcondition holds for \( \mathcal {H}^{\prime \prime } \), \( \mathcal {L} \), and v1, which is sufficient to assume that the second subevaluation does not fail either (by the same reasoning as for the first). Again, if it succeeds, the side condition, and hence the step, will not fail.
(b) If the evaluation succeeds, we know that its subevaluations must have succeeded, so that by the induction hypothesis, part (b), the postcondition holds for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \). It also holds for v, since by T-Bool and N-Bool, true and false are typed and numbered as required by T-EqId and N-EqId. \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {(} C \texttt {)}\, e} \end{equation*} \)
(a) Type casts are covered by a regular evaluation rule, E-TCastc, and an exceptional evaluation rule, E-TCastEc, which differ only in the last side condition. By the precondition of the evaluation step, the well-formedness rules T-TCast and N-TCast, and the induction hypothesis, part (a), we may assume that the (common) subevaluation of E-TCastc and E-TCastEc does not fail. If the substep succeeds, we can rely on the induction hypothesis, part (b), for assuming that the postcondition of the subevaluation holds for \( \mathcal {H}^{\prime } \), \( \mathcal {L} \), and v; specifically, that v is countable (η ≠ ηϵ), so that the first side condition succeeds. From this, Lemma 1, and (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) for \( \mathcal {H}^{\prime } \) we can infer that \( \mathcal {H}^{\prime }[l_i][f_\chi ] \) is well-defined for all i, so that the second side condition of either E-TCastc or E-TCastEc succeeds; in neither case, evaluation fails.
(b) If evaluation succeeds, then by E-TCastc. We know that its subevaluation must have succeeded, so that by assuming the induction hypothesis, part (b), (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)), (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)), and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) hold for \( \mathcal {H}^{\prime } \), \( \mathcal {L} \), and v (because v = l1 … ln), as required. (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) for v = l1 … ln follows directly from T-Objs and the success of the second side condition of E-TCastc. \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {(} \eta \texttt {)}\, e} \end{equation*} \)
(a) Since a number cast to ηϵ is ruled out by syntax, evaluation does not fail for reasons analogous to those stated for type cast above (eased by the absence of heap access in the second side condition).
(b) If evaluation succeeds, (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)), (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)), and (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) hold for \( \mathcal {H}^{\prime } \), \( \mathcal {L} \), and l1 … ln for reasons analogous to type cast. Because success must have been through E-NCastc, we know that its second side condition holds; from this and N-Objs it follows (by case analysis on η) that \( \vdash {l}_1\, \ldots \, {l}_{n} {\#}\eta \), and thus that (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) holds for l1 … ln. \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {|} e \texttt {|}} \end{equation*} \)
(a) The applicable rule is E-Countc. Given the precondition of the step, the well-formedness rules T-Count and N-Count, and the induction hypothesis, part (a), we may assume that the subevaluation does not fail. If it succeeds, we know from η ≠ ηϵ and the numbering of values (N-Objs, N-Bool, and N-Int) that v is a number of objects, so that the side condition v = l1 … ln succeeds and (a) holds.
(b) If evaluation succeeds, we know that its subevaluation must have succeeded, so that by assuming the induction hypothesis, part (b), (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) hold for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \). (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) trivially hold, because by T-Int, \( \mathcal {H}^{\prime }\vdash n :\mathit {int} \) as required by T-Count, and by N-Int, \( \vdash n {\#}\eta _\epsilon \) as required by N-Count.
This concludes all cases and, with it, the proof of Lemma 4.□
For sequences of statements s1 … sn, heaps \( \mathcal {H} \), and locals stores \( \mathcal {L} \) such that

and for all d ≥ 0,
(a) \( \lnot \big (\langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow _{d}\; \uparrow \!\! \mathit {\mathit {fail}} \,\big) \)
(b) if \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow _{d} \langle \mathcal {H}^{\prime },\mathcal {L}^{\prime } \rangle \), then

As for Lemma 4, the proof is by induction on d. For all s1 … sn, \( \langle \mathcal {H}, \mathcal {L}, {s}_1\, \ldots \, {s}_{n}\rangle \Rightarrow _{0}\; \uparrow \!\! \mathit {\mathit {exhausted}} \) by the exhaustion axioms, so that (a) trivially holds and (b) is vacuously true for d = 0. For d > 0, the proof is structured by case analysis on the syntactic forms of (sequences of) statements, relying on Lemma 4 for all statements that contain expressions. As with Lemma 4, I will refer to assumptions (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) through (\( \color{#4863A0}{s\checkmark \!\!_\eta :} \)) (where (\( \color{#4863A0}{\mathcal {H}\checkmark :} \)) and (\( \color{#4863A0}{\mathcal {L}\checkmark :} \)) are the same as for Lemma 4) as the precondition of an execution step, and to (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}^{\prime }\checkmark } \)) as its postcondition (where (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) is the same as for Lemma 4). Also, E-Namec refers to the counter-based version of E-Name. \( \begin{equation*} \boxed{\vphantom{a^c}\epsilon } \end{equation*} \)
(a) Execution of no statement is covered by E-NoStatc, trivially succeeds, and therefore does not fail.
(b) Given that nothing changes, the postcondition follows immediately from the precondition. \( \begin{equation*} \boxed{\vphantom{a^c}\vphantom{a^C}s\; s^{\prime }\, {s}_1\, \ldots \, {s}_{n}} \end{equation*} \)
(a) Two or more statements are covered by E-Seqc. Given the precondition of the execution step and the well-formedness rules T-Seq and N-Seq, we can assume by the induction hypothesis, part (a), that the first subexecution does not fail. If it succeeds, by the induction hypothesis, part (b), we may assume that the postcondition holds for \( \mathcal {H}^{\prime \prime } \) and \( \mathcal {L}^{\prime \prime } \), which is sufficient to assume that, by the same reasoning as for the first, the second subexecution does not fail.
(b) If the execution succeeds, we know that both of its subexecutions must have succeeded, so that by assuming the induction hypothesis, part (b), the postcondition holds for \( \mathcal {H}^{\prime } \) and \( \mathcal {L}^{\prime } \) (as it does for \( \mathcal {H}^{\prime \prime } \) and \( \mathcal {L}^{\prime \prime } \) above). \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {if\;(} e \texttt {)\;\lbrace } {s}_1\, \ldots \, {s}_{n} \texttt {\rbrace \;else\;\lbrace }{s^{\prime }}_1\, \ldots \, {s^{\prime }}_{n^{\prime }}\texttt {\rbrace }} \end{equation*} \)
(a) The applicable rules E-IfTc and E-IfFc share the first substep, evaluation of e. From the precondition of the step and the well-formedness rules T-If and N-If, we know that the precondition for the evaluation of e is satisfied, so that we can assume by the induction hypothesis and Lemma 4 that this first substep does not fail. If it succeeds, we can assume by Lemma 4 and the typing of values that v must be true or false so that the side condition of one of E-IfTc and E-IfFc succeeds, and also that (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}^{\prime }\checkmark } \)) hold for \( \mathcal {H}^{\prime \prime } \) and \( \mathcal {L} \). This, together with T-If and N-If, suffices to assume that the precondition of the next substep, executing either s1 … sn or \( {s^{\prime }}_1\, \ldots \, {s^{\prime }}_{n^{\prime }} \) (selected by v), is met, and hence by the induction hypothesis, part (a), that the second substep does not fail.
(b) If the execution step succeeds, its substeps must have succeeded; the postcondition of the step follows from the induction hypothesis, part (b), applied to the second substep. \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {while\;(} e \texttt {)\;\lbrace } {s}_1\, \ldots \, {s}_{n}\texttt {\rbrace }} \end{equation*} \)
(a) and (b) follow directly from the if-then-else case, to which the applicable rule E-Whilec forwards. \( \begin{equation*} \boxed{\vphantom{a^c}{e_0}^{C}\!\!\texttt {.}m\texttt {(}e_1\texttt {,}\ldots \texttt {,}e_p\texttt {)}\texttt {;}} \end{equation*} \)
(a) The applicable rule E-MethInvocc forwards to method application, an evaluation step. From the precondition of the execution step and the well-formedness rules T-MethInvoc and N-MethInvoc we know that the precondition for the evaluation substep is satisfied, so that we can assume by the induction hypothesis and Lemma 4 that the evaluation, and hence the execution, does not fail.
(b) If the execution step succeeds, its subevaluation must have succeeded; (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) follows directly from Lemma 4 applied to the sub-step and (\( \color{#4863A0}{:\mathcal {L}^{\prime }\checkmark } \)) follows from (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and Lemma 3 (\( \mathcal {L} \) does not change). \( \begin{equation*} \boxed{\vphantom{a^c}{\texttt {this}}^{C}\!\!\texttt {.}f\,\texttt {=}\;e\texttt {;}} \end{equation*} \)
(a) Field assignment is handled by E-FldAssignc. The first side condition, fetching the location of the receiver, does not fail for the same reasons as for the case of evaluating field access. Given the precondition of this execution step and the well-formedness rules T-FldAssign and N-FldAssign, the evaluation of the expression to be assigned does not fail by Lemma 4 and the induction hypothesis. If it succeeds the second side condition does not fail because of Lemma 1 and because we get (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) for \( \mathcal {H}^{\prime \prime } \) from Lemma 4.
(b) If execution of the assignment succeeds, we may assume from Lemma 4 that (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) hold for \( \mathcal {H}^{\prime \prime } \) and \( \mathcal {L} \); we thus only need to show that they hold for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \), too. The update of \( \mathcal {H}^{\prime \prime } \), \( \mathcal {H}^{\prime } \), is well-typed and well-numbered by (\( \color{#4863A0}{:v\checkmark \!\!_\tau } \)) and (\( \color{#4863A0}{:v\checkmark \!\!_\eta } \)) for v in conjunction with T-FldAssign and N-FldAssign, where the latter specify the required type and number for values of C.f. It follows that (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) holds for \( \mathcal {H}^{\prime } \); (\( \color{#4863A0}{:\mathcal {L}^{\prime }\checkmark } \)) for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \) follows from Lemma 3. \( \begin{equation*} \boxed{\vphantom{a^c}\vphantom{a^C}x\;\texttt {=}\;e\texttt {;}} \end{equation*} \)
(a) Variable assignment is handled by E-VarAssignc. Given the precondition of this execution step and the well-formedness rules T-VarAssign and N-VarAssign, the evaluation of the assigned expression does not fail by the the induction hypothesis and Lemma 4. If it succeeds, we may assume that, for the same reasons as for evaluating variable access, \( x\propto dom(\mathcal {L}) \), so that updating \( \mathcal {L} \) does not fail.
(b) If the step succeeds, we may assume from Lemma 4 that (\( \color{#4863A0}{:\mathcal {H}^{\prime }\checkmark } \)) and (\( \color{#4863A0}{:\mathcal {L}^{\prime }\checkmark } \)) hold for \( \mathcal {H}^{\prime } \) and \( \mathcal {L} \); we thus only need to show that (\( \color{#4863A0}{:\mathcal {L}^{\prime }\checkmark } \)) holds for \( \mathcal {L}^{\prime } \), too. From (\( \color{#4863A0}{:\mathcal {L}\checkmark } \)) for \( \mathcal {L} \), \( \mathcal {L}[x]=\rho \,v^{\prime } \), the well-formedness rules T-VarAssign and N-VarAssign (where \( \mathcal {T} \) and \( \mathcal {N} \) are supplied by \( \mathcal {T}(\mathcal {L}) \) and \( \mathcal {N}(\mathcal {L}) \)), Lemma 4, and the fact that the range of x is not affected by the update, we know that \( \mathcal {L}^{\prime }=\mathcal {L}[x \mapsto v] \) is well-formed. \( \begin{equation*} \boxed{\vphantom{a^c}\texttt {return}\;e\texttt {;}} \end{equation*} \)
(a) and (b) analogous to variable assignment, using E-Returnc
This concludes all cases and, with it, the proof of Lemma 5.□
I can now come to the formulation and proof of the main theorem.
Num) Given a well-formed program P and an expression e such that ϵ⊢e: τ and \( \epsilon \vdash e{\#}\eta \) for some τ and η, if evaluation of ⟨ϵ, ϵ, e⟩ terminates, then either ⟨ϵ, ϵ, e⟩⇒ ↑X with X being a conceded exception, or \( {\langle \epsilon , \epsilon , e\rangle } \Rightarrow \langle \mathcal {H}, v\rangle \) with \( \mathcal {H}\vdash v:\tau \) and \( \vdash v{\#}\eta \).
That the evaluation of ⟨ϵ, ϵ, e⟩ terminates implies that there exists a d such that ¬(⟨ϵ, ϵ, e⟩⇒d ↑exhausted). Because such an evaluation cannot have used the exhaustion axioms, there exists a corresponding derivation using the standard relation ⇒ [18]. Therefore, we know that either ⟨ϵ, ϵ, e⟩⇒ ↑X or \( {\langle \epsilon , \epsilon , e\rangle } \Rightarrow \langle \mathcal {H}, v\rangle \). In the first case, fail is ruled out as the value of X by Lemma 4 so that only conceded exceptions remain, as required; in the second case, Lemma 4 makes sure that \( \mathcal {H}\vdash v:\tau \) and \( \vdash v{\#}\eta \), as required.□
Footnotes
1 Note that, while wildcard types may be specific to Java, covariance of containers is generally incompatible with writing into them [62].
Footnote2 One might argue that programming languages do not come with grammatical categories at all; however, type may be viewed as one.
Footnote3 In fact, even in Smalltalk, the language in which “everything is an object”, small integers and characters are usually implemented as primitive values (“immediates”) rather than (references to) objects, sometimes leading to confusing behavior (small numbers are identical if they are equal, while big numbers are not; note that equating the value of numbers with their identity does not help here). Generally, different languages make different distinctions, but few (if any) manage to fully unify objects and values [58].
Footnote4 Countable and uncountable are not to be confused with countably many and uncountably many.
Footnote5 Note that in many object-oriented programming languages including Java, boxing is a prerequisite to storing primitive values in containers other than arrays; in these languages, making the boxing of primitive values a prerequisite for having numbers of them would be consistent with their design.
Footnote6 Readers acquainted with Hehner’s bunches [26, 27] I refer to Section 7.1.1 for a discussion of analogies and differences.
Footnote7 I use Greek eta (η), rather than nu (ν), for number to avoid confusion with Latin v, which, beginning in Section 5.3, I will use for values.
Footnote8 The method number specifier may actually be viewed as a receiver annotation, i.e., as an annotation of
Footnotethis (which becomesthese for plural methods; see below).9 Subgoal is a Prolog term referring to a predicate that is to be resolved, or proved, in the course of proving a
Footnotegoal (predicate) matching the head of the rule in whose body the subgoal occurs.10 The mechanisms are not the same: unlike exceptions, failure is “propagated” via backtracking, so that equivalence depends on the absence of choice points. This is granted by the premise that rule selection is deterministic.
Footnote11 For this, the evaluation of side conditions also assumes a closed world, meaning that undefinedness, here caused by \( x\not\propto \mathit {dom}(\mathcal {L}) \), is interpreted as falseness.
Footnote12 In fact, as pointed out by Hehner [26, p. 17]: “All sets are elements; not all bunches are elements; that is, the difference between sets and bunches.” Substituting “element” with “object”, the same holds for numbers of objects: Only a singular number of objects (i.e., a number of objects with count 1) is an object.
Footnote13 Note that this does not make numbers of objects objects: The place of a collection is a variable like any other (except perhaps that it appears to be unnamed), and may thus hold any numbers of objects. On the other hand, given that numbers of objects with static numbers other than
Footnote! cannot be tested for identity (N-EqId), implementing collections of numbers of objects with identity-based set semantics is impossible.14 https://kotlinlang.org/docs/kotlin-docs.pdf.
Footnote15 The use of c and c + 1 instead of c − 1 and c follows Siek [52] and saves us the side condition c > 0 in all rules.
16 Note that in the following (like for most parts of Section 5), the program P and the requirement that P is well-formed (which includes \( P\checkmark \!\!_\tau \) and \( P\checkmark \!\!_\eta \); see Section 5.1) are left implicit.
- [1] . 2017. Type soundness proofs with definitional interpreters. In Proceedings of the 44th ACM SIGPLAN Symposium on Principles of Programming Languages. and (Eds.), ACM, 666–679.
DOI: http://dl.acm.org/citation.cfm?id=3009866Google ScholarDigital Library
- [2] . 2000. The Java Programming Language, Third Edition. Addison-Wesley.Google Scholar
- [3] . 2011. Verifying multi-object invariants with relationships. In Proceedings of the 25th European Conference on Object-oriented Programming, (Ed.). Springer, 358–382.
DOI: Google ScholarCross Ref
- [4] . 2005. The essence of data access in Comega. In Proceedings of the 19th European Conference on Object-Oriented Programming, (Ed.). Springer, 287–311.
DOI: Google ScholarDigital Library
- [5] . 2007. Lost in translation: Formalizing proposed extensions to c#. In Proceedings of the 22nd Annual ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, , , , and (Eds.). ACM, 479–498.
DOI: Google ScholarDigital Library
- [6] . 2010. Adding dynamic types to \( C^{\#} \). In Proceedings of the 24th European Conference on Object-Oriented Programming, (Ed.). Springer, 76–100.
DOI: Google ScholarCross Ref
- [7] . 2003. MJ: An Imperative Core Calculus for Java and Java with Effects.
Technical Report UCAM-CL-TR-563. University of Cambridge, Computer Laboratory.Google Scholar - [8] . 2005. First-class relationships in an object-oriented language. In Proceedings of the 19th European Conference on Object-Oriented Programming, (Ed.). Springer, 262–286.
DOI: Google ScholarDigital Library
- [9] . 2004. Encapsulating non-determinism in functional logic computations. J. Funct. Log. Program. 2004 (2004), 1–28. Retrieved from http://danae.uni-muenster.de/lehre/kuchen/JFLP/articles/2004/S04-01/A2004-06/JFLP-A2004-06.pdf.Google Scholar
- [10] 1987. No silver bullet - essence and accidents of software engineering. IEEE Computer 20, 4 (1987), 10–19.
DOI: Google ScholarDigital Library
- [11] . 1995. On binary methods. TAPOS 1, 3 (1995), 221–242.Google Scholar
Digital Library
- [12] . 2012. Object constraint language (OCL): A definitive guide. In Proceedings of the 12th International School on Formal Methods for the Design of Computer, Communication, and Software Systems, , , and (Eds.). Springer, 58–90.
DOI: Google ScholarDigital Library
- [13] . 1883. Ueber unendliche, lineare Punktmannichfaltigkeiten. Mathematische Annalen 21, 4 (1883), 545–591.
DOI: Google ScholarCross Ref
- [14] . 2013. Pretty-big-step semantics. In Proceedings of the 22nd European Symposium on Programming, ESOP 2013, Held as Part of the European Joint Conferences on Theory and Practice of Software, and (Eds.). Springer, 41–60.
DOI: Google ScholarDigital Library
- [15] . 1976. The entity-relationship model - toward a unified view of data. ACM Transactions on Database Systems 1, 1 (1976), 9–36.
DOI: Google ScholarDigital Library
- [16] . 2016. A calculus for variational programming. In Proceedings of the 30th European Conference on Object-Oriented Programming, and (Eds.). Schloss Dagstuhl - Leibniz-Zentrum für Informatik, 6:1–6:28.
DOI: Google ScholarCross Ref
- [17] . 2002. More dynamic object reclassification: Fickle||. ACM Transactions on Programming Languages and Systems 24, 2 (2002), 153–191.
DOI: Google ScholarDigital Library
- [18] . 2006. A virtual class calculus. In Proceedings of the 33rd ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL 2006, Charleston, and (Eds.). ACM, 270–282.
DOI: Google ScholarDigital Library
- [19] . 2003. Declaring and checking non-null types in an object-oriented language. In Proceedings of the 2003 ACM SIGPLAN Conference on Object-Oriented Programming Systems, Languages and Applications, and (Eds.). ACM, 302–312.
DOI: Google ScholarDigital Library
- [20] . 1999. A programmer’s reduction semantics for classes and mixins. In Proceedings of the Formal Syntax and Semantics of Java(
Lecture Notes in Computer Science , Vol. 1523), (Ed.). Springer, 241–269.DOI: Google ScholarCross Ref
- [21] . 1999. Refactoring - Improving the Design of Existing Code. Addison-Wesley. Retrieved from http://martinfowler.com/books/refactoring.html.Google Scholar
Digital Library
- [22] . 2011. Domain-Specific Languages. Addison-Wesley. Retrieved form http://vig.pearsoned.com/store/product/1,1207,store-12521_isbn-0321712943,00.html.Google Scholar
Digital Library
- [23] . 2017. APLicative programming with naperian functors. In Proceedings of the 26th European Symposium on Programming, ESOP 2017, Held as Part of the European Joint Conferences on Theory and Practice of Software, ETAPS 2017, (Ed.). Springer, 556–583.
DOI: Google ScholarDigital Library
- [24] . 2014. Unifying and generalizing relations in role-based data modeling and navigation. In Proceedings of the 7th International Conference on Software Language Engineering, , , , and (Eds.). Springer, 241–260.
DOI: Google ScholarCross Ref
- [25] . 2019. Declarative Specification of Information System Data Models and Business Logic. Ph. D. Dissertation. Delft University of Technology, Netherlands.
DOI: Google ScholarCross Ref
- [26] . 1993. A Practical Theory of Programming. Springer-Verlag, New York.
DOI: Google ScholarCross Ref
- [27] . 1981. Bunch theory: A simple set theory for computer science. Information Processing Letters 12, 1 (1981), 26–30.
DOI: Google ScholarCross Ref
- [28] . 1997. Adapting big-step semantics to small-step style: Coinductive interpretations and “Higher-Order” derivations. Electronic Notes in Theoretical Computer Science 23, 3 (1997), 121.
DOI: Google ScholarDigital Library
- [29] . 2001. Featherweight Java: A minimal core calculus for Java and GJ. ACM Transactions on Programming Languages and Systems 23, 3 (2001), 396–450.
DOI: Google ScholarDigital Library
- [30] . 1986. A simple technique for handling multiple polymorphism. In Proceedings of the Conference on Object-Oriented Programming Systems, Languages, and Applications, (Ed.). ACM, 347–349.
DOI: Google ScholarDigital Library
- [31] . 1962. A Programming Language. John Wiley & Sons, Inc.Google Scholar
Digital Library
- [32] . 2006. Software Abstractions - Logic, Language, and Analysis. MIT Press. Retrieved from http://mitpress.mit.edu/catalog/item/default.asp?ttype=2&tid=10928.Google Scholar
Digital Library
- [33] . 2006. A machine-checked model for a Java-like language, virtual machine, and compiler. ACM Trans. Program. Lang. Syst. 28, 4 (2006), 619–695.
DOI: Google ScholarCross Ref
- [34] . 2013. Combining form and function: Static types for JQuery programs. In Proceedings of the 27th European Conference on Object-Oriented Programming, (Ed.). Springer, 79–103.
DOI: Google ScholarDigital Library
- [35] . 1993. Cardinality constraints in semantic data models. Data & and Knowledge Engineering 11, 3 (1993), 235–270.
DOI: Google ScholarDigital Library
- [36] . 2011. The world according to LINQ. Communications of the ACM 54, 10 (2011), 45–51.
DOI: Google ScholarDigital Library
- [37] . 2003. Unifying tables, objects and documents. In Proceedings of Declarative Programming in the Context of OO Languages. Retrieved from http://research.microsoft.com/apps/pubs/default.aspx?id=79586.Google Scholar
- [38] . 2016. On essential configuration complexity: Measuring interactions in highly-configurable systems. In Proceedings of the 31st IEEE/ACM International Conference on Automated Software Engineering, , , and (Eds.). ACM, 483–494.
DOI: Google ScholarDigital Library
- [39] . 2017. C# Language Specification (5th Edition).
Standard ECMA-334:2017. European Computer Manufacturers Association, Geneva, CH. Retrieved from https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-334.pdf.Google Scholar - [40] . 2003. Concepts in Programming Languages. Cambridge University Press.Google Scholar
- [41] . 2001. A theory of bunches. Acta Informatica 37, 8 (2001), 541–561.
DOI: Google ScholarDigital Library
- [42] . 1998. Javalight is type-safe - definitely. In Proceedings of the 25th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, and (Eds.). ACM, 161–170.
DOI: Google ScholarDigital Library
- [43] . 2000. Basic relationship patterns. In Proceedings of the Pattern Languages of Program Design, , , and (Eds.), Vol. 4. Addison-Wesley, 73–89.Google Scholar
- [44] . 1998. Flexible alias protection. In Proceedings of the 12th European Conference on Object-Oriented Programming, (Ed.). Springer, 158–185.
DOI: Google ScholarCross Ref
- [45] . 2011. Unified Modeling Language 2.4.1.
Specification . OMG (Object Management Group). Retrieved from http://www.omg.org/spec/UML/2.4.1/.Google Scholar - [46] . 2012. Object Constraint Language 2.4.
Specification . OMG (Object Management Group). Retrieved from http://www.omg.org/spec/OCL/.Google Scholar - [47] . 2002. Types and Programming Languages. MIT Press.Google Scholar
Digital Library
- [48] . 1987. Relations as semantic constructs in an object-oriented language. In Proceedings of the Conference on Object-Oriented Programming Systems, Languages, and Applications, (Ed.). ACM, 466–481.
DOI: Google ScholarDigital Library
- [49] . 1988. Controlling propagation of operations using attributes on relations. In Proceedings of the Conference on Object-Oriented Programming Systems, Languages, and Applications, (Ed.). ACM, 285–296.
DOI: Google ScholarDigital Library
- [50] . 2008. Bridging the object-relational divide. ACM Queue 6, 3 (2008), 18–28.
DOI: Google ScholarDigital Library
- [51] . 2013. The billion-dollar fix - safe modular circular initialisation with placeholders and placeholder types. In Proceedings of the 27th European Conference on Object-Oriented Programming, (Ed.). Springer, 205–229.
DOI: Google ScholarDigital Library
- [52] . 2013. Type Safety in Three Easy Lemmas. Blog Post. Retrieved from http://siek.blogspot.com/2013/05/type-safety-in-three-easy-lemmas.html.Google Scholar
- [53] . 2014. An array-oriented language with static rank polymorphism. In Proceedings of the 23rd European Symposium on Programming Languages and Systems, (Ed.). Springer, 27–46.
DOI: Google ScholarDigital Library
- [54] . 1992. Non-determinism in functional languages. Computer Journal 35, 5 (1992), 514–523.
DOI: Google ScholarDigital Library
- [55] 2017. It’s time for a new old language. In Proceedings of the 22nd ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming, and (Eds.). ACM, 1. Retrieved from https://www.youtube.com/watch?v=7HKbjYqqPPQ
(abstract of the keynote; quote is from the video recording) .Google Scholar - [56] . 2013. Content over container: Object-oriented programming with multiplicities. In Proceedings of the ACM Symposium on New Ideas in Programming and Reflections on Software, Onward! 2013, part of SPLASH ’13, Indianapolis, , , and (Eds.). ACM, 173–186.
DOI: Google ScholarDigital Library
- [57] . 2015. None, one, many - what’s the difference, anyhow? In Proceedings of the 1st Summit on Advances in Programming Languages, , , , , and (Eds.). Schloss Dagstuhl - Leibniz-Zentrum für Informatik, 294–308.
DOI: Google ScholarCross Ref
- [58] . 2021. The kingdoms of objects and values. In Proceedings of the 2021 ACM SIGPLAN International Symposium on New Ideas, New Paradigms, and Reflections on Programming and Software, Onward! 2021, (Ed.). ACM, 125–135.
DOI: Google ScholarDigital Library
- [59] . 2014. Multitudes of objects: first implementation and case study for java. Journal of Object Technology 13, 5 (2014), 1: 1–33.
DOI: Google ScholarCross Ref
- [60] . 2011. Freedom before commitment: A lightweight type system for object initialisation. In Proceedings of the 26th Annual ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, OOPSLA 2011, part of SPLASH 2011, and (Eds.). ACM, 1013–1032.
DOI: Google ScholarDigital Library
- [61] . 2010. JavaFXTM Developer’s Guide. Addison-Wesley Professional.Google Scholar
Digital Library
- [62] . 2004. Adding wildcards to the java programming language. Journal of Object Technology 3, 11 (2004), 97–116.
DOI: Google ScholarCross Ref
- [63] . 2010. Harnessing emergence for manycore programming: Early experience integrating ensembles, adverbs, and object-based inheritance. In Proceedings of the Companion to the 25th Annual ACM SIGPLAN Conference on Object-Oriented Programming, Systems, Languages, and Applications, , , and (Eds.). ACM, 19–26.
DOI: Google ScholarDigital Library
- [64] . 1976. The semantics of predicate logic as a programming language. Journal of the ACM 23, 4 (1976), 733–742.
DOI: Google ScholarDigital Library
- [65] . 1992. The essence of functional programming. In Proceedings of the Conference Record of the 19th Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages. 1992, (Ed.). ACM Press, 1–14.
DOI: Google ScholarDigital Library
Index Terms
Containerless Plurals: Separating Number from Type in Object-Oriented Programming
Recommendations
Type-theoretic foundations for concurrent object-oriented programing
OOPSLA '94: Proceedings of the ninth annual conference on Object-oriented programming systems, language, and applicationsA number of attempts have been made to obtain type systems for object-oriented programming. The view that lies common is “object-oriented programming = λ-calculus + record.” Based on an analogous view “concurrent object-oriented programming = concurrent ...






















Comments