149 Featherweight Go ROBERT GRIESEMER, Google, USA RAYMOND HU, University of Hertfordshire, UK WEN KOKKE, University of Edinburgh, UK JULIEN LANGE, Royal Holloway, University of London, UK IAN LANCE TAYLOR, Google, USA BERNARDO TONINHO, NOVA-LINCS, FCT-NOVA, Universidade Nova de Lisboa, Portugal PHILIP WADLER, University of Edinburgh, UK NOBUKO YOSHIDA, Imperial College London, UK We describe a design for generics in Go inspired by previous work on Featherweight Java by Igarashi, Pierce, and Wadler. Whereas subtyping in Java is nominal, in Go it is structural, and whereas generics in Java are defined via erasure, in Go we use monomorphisation. Although monomorphisation is widely used, we are one of the first to formalise it. Our design also supports a solution to The Expression Problem. CCS Concepts: • Theory of computation→ Program semantics; Type structures; • Software and its engineering→ Polymorphism. Additional Key Words and Phrases: Go, Generics, Monomorphisation 1 INTRODUCTION Google introduced the Go programming language in 2009 [Griesemer et al. 2009; The Go Team 2020]. Today it sits at position 12 on the Tiobe Programming Language Index and position 10 on the IEEE Spectrum Programming Language Ranking (Haskell sits at positions 41 and 29, respectively). Recently, the Go team mooted a design to extend Go with generics [Taylor and Griesemer 2019], and Rob Pike wrote Wadler to ask: Would you be interested in helping us get polymorphism right (and/or figuring out what “right” means) for some future version of Go? This paper is our response to that question. Two decades ago, Igarashi, Pierce, and Wadler [1999; 2001], introduced Featherweight Java. They considered a tiny model of Java (FJ), extended that model with generics (FGJ), and translated FGJ to FJ (via erasure). In their footsteps, we introduce Featherweight Go. We consider a tiny model of Go (FG), extend that model with generics (FGG), and translate FGG to FG (via monomorphisation). Go differs in interesting ways from Java. Subtyping in Java is nominal, whereas in Go it is structural. Casts in Java correspond to type assertions in Go: casts in Java with generics are Authors’ addresses: Robert Griesemer, Google, USA; Raymond Hu, University of Hertfordshire, School of Engineering and Computer Science, Hatfield, UK, r.z.h.hu@herts.ac.uk; Wen Kokke, University of Edinburgh, Laboratory for Foundations of Computer Science, Edinburgh, UK, wen.kokke@ed.ac.uk; Julien Lange, Royal Holloway, University of London, Department of Computer Science, Egham, UK, julien.lange@rhul.ac.uk; Ian Lance Taylor, Google, USA; Bernardo Toninho, Departamento de Informática, NOVA-LINCS, FCT-NOVA, Universidade Nova de Lisboa, Portugal, btoninho@fct.unl.pt; Philip Wadler, University of Edinburgh, School of Informatics, Edinburgh, UK, wadler@inf.ed.ac.uk; Nobuko Yoshida, Imperial College London, Computing, London, UK, n.yoshida@imperial.ac.uk. Permission to make digital or hard copies of part or all of this work for personal or classroom use is granted without fee provided that copies are not made or distributed for profit or commercial advantage and that copies bear this notice and the full citation on the first page. Copyrights for third-party components of this work must be honored. For all other uses, contact the owner/author(s). © 2020 Copyright held by the owner/author(s). 2475-1421/2020/11-ART149 https://doi.org/10.1145/3428217 Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:2 Griesemer et al. restricted to support erasure, whereas type assertions in Go with generics are unrestricted thanks to monomorphisation. Monomorphisation is widely used, but we are among the first to formalise it. The Expression Problem was first formulated by Wadler [1998] in the context of Java, though Java never supported a solution; our design does. We provide a full formal development: for FG and FGG, type and reduction rules, and preservation and progress; for monomorphisation, a formal translation from FGG to FG that preserves types and is a bisimulation. The complete proofs are available in [Griesemer et al. 2020]. Structural subtyping. Go is based on structures and interface types. Whereas most programming languages use nominal subtyping, Go is unique among mainstream typed programming languages in using structural subtyping. A structure implements an interface if it defines all the methods specified by that interface, and an interface implements another if the methods of the first are a superset of the methods specified by the second. In Java, the superclasses of a class are fixed by the declaration. If lists are defined before collections, then one cannot retrofit collections as a superclass of lists—save by rewriting and recompiling the entire library. In Haskell the superclasses of a type class are fixed by the declaration. If monads are defined before functors, then one cannot retrofit functors as a superclass of monads—save by rewriting and recompiling the entire library. In contrast, in Go one might define lists or monads first, and later introduce collections or functors as an interface that the former implements—without rewriting or recompiling the earlier code. The Expression Problem. The Expression Problem was formulated by Wadler [1998]. It gave a name to issues described by Cook [1990], Reynolds [1994], and Krishnamurthi et al. [1998], and became the basis of subsequent work by Torgersen [2004], Zenger and Odersky [2004], Swierstra [2008], and many others. Wadler defines The Expression Problem this way: The goal is to define a data type by cases, where one can add new cases to the data type and new functions over the data type, without recompiling existing code, and while retaining static type safety. And motivates its interest as follows: Whether a language can solve the Expression Problem is a salient indicator of its capacity for expression. One can think of cases as rows and functions as columns in a table. In a functional language, the rows are fixed (cases in a datatype declaration) but it is easy to add new columns (functions). In an object-oriented language, the columns are fixed (methods in a class declaration) but it is easy to add new rows (subclasses). We want to make it easy to add either rows or columns. One can come close to solving The Expression Problem in Go as it exists now, using dynamic checking via type assertions. We show how to provide a fully static solution with generics. We had to adjust our design: our first design for generics used nonvariant matching on bounds in receivers of methods, but to solve The Expression Problem we had to relax this to covariant matching. Monomorphisation. FGJ translates to FJ via erasure, whereas FGG translates to FG via monomor- phisation. Two instances List and List in FGJ both translate to List in FJ (where <> are punctuation), whereas two instances List(int) and List(bool) in FGG translate to separate types List and List in FG (where () are punctuation, but <> are taken as part of the name). Erasure is more restrictive than monomorphisation. In Java with generics, a cast (a)x is illegal if a is a type variable, whereas in Go with generics, the equivalent type assertion x.(a) is permitted. Erasure is often less efficient than monomorphisation. In Java with generics, all type variables are boxed, whereas in Go with generics type variables may instantiate to be unboxed. However, Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:3 erasure is linear in the size of the code, whereas monomorphisation can suffer an exponential blowup; and erasure is suited to separate compilation, whereas monomorphisation requires the whole program. We choose to look at monomorphisation in the first instance, because it is simple, efficient, and the first design looked at by the Go team. Other designs offer other tradeoffs, e.g., the restriction on a type assertion x.(a) could also be avoided by passing runtime representations of types. This solution avoids exponential blowup and offers better support for separate compilation, but at a cost in efficiency and complexity. We expect the final solution will involve a mix of both monomorphisation and runtime representations of types, see Section 8 for more details. Template expansion in C++ [Stroustrup 2013, Chapter 26] corresponds to monomorphisation. Generics in .NET are implemented by a mixture of erasure and monomorphisation [Kennedy and Syme 2001]. The MLton compiler for Standard ML [Cejtin et al. 2000] and the Rust programming language [The Rust Team 2017] both apply techniques closely related to monomorphisation, as described by Fluet [2015] and Turon [2015] on web pages and blog posts. We say more about related work in Section 7, but we have found only a handful of peer-reviewed publications that touch on formalisation of monomorphisation. Monomorphisation is possible only when it requires a finite set of instances of types and methods. We believe we are the first to formalise computation of instance sets and determination of whether they are finite. The bookkeeping required to formalise monomorphisation of instances and methods is not trivial. Monomorphising an interface with type parameters that contains a method with type parameters may require different instances of the interfaces to contain different instances of the methods. It took us several tries over many months to formalise it correctly. While the method for monomorphisation described here is specialised to Go, we expect it to be of wider interest, since similar issues arise for other languages and compilers such as C++, .Net, MLton, or Rust. Featherweight vs complete. A reviewer of an earlier revision of this paper wrote: It is also quite common for semantics to strive for “completeness”, instead of being “featherweight”. There is a lot of value in having featherweight semantics, but the argument for completeness is that it helps language designers understand bad interactions between features. (For example, Amin and Tate [2016] recently showed that Java generics are unsound, but the bug is beyond the scope of Featherweight Generic Java.) We agree with these words. Since the reviewwas a reject, we deduce an implicit claim that it is better to be complete. Here, with respect, we disagree. We argue both “featherweight” and “complete” descriptions have value. As evidence, compare citations counts for the paper on Featherweight Java, Igarashi et al. [2001], with the four most-cited papers on more complete models, Drossopoulou and Eisenbach [1997]; Flatt et al. [1998]; Nipkow and von Oheimb [1998]; Syme [1999]: 1070 as compared with 549, 248, 174, 158, respectively (Google Scholar, April 2020). Impact. The original proposal for generics in Go [Taylor and Griesemer 2019] was based on contracts, which are syntactically convenient but lack a clear semantics. One result of our work is that the new proposal [Taylor and Griesemer 2020] is based on interfaces, which are already well defined in Go. After we submitted the draft of this paper, Griesemer wrote to Wadler: I want to thank you and your team for all the type theory work on Go so far—it really helped clarify our understanding to a massive degree. So thanks! Another result is the proposal for covariant receiver typing, a feature required by The Expression Problem. It is not part of the Go team’s current design, but they have noted it is backward compatible and are considering adding it in the future. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:4 Griesemer et al. In this paper we adopt the syntax originally proposed by the Go team in July 2019 [Taylor and Griesemer 2019]. In September 2020 [Taylor and Griesemer 2020], they proposed a revised syntax where type parameters are declared within square brackets and the type keyword is omitted. We prefer the new syntax, but retained the old since our artifact uses the old syntax and artifact evaluation was already complete. Outline. Section 2 introduces FG and FGG, presents a solution to The Expression Problem, and introduces monomorphisation. Sections 3 and 4 present FG and FGG; we give formal rules for types and reductions, and prove preservation and progress. Section 5 presents monomorphisation, which translates FGG back to FG; we prove the translation preserves types and is a bisimulation. Section 6 describes our prototype implementation. Section 7 describes related work. Section 8 concludes. Additional examples and details of all proofs are available in [Griesemer et al. 2020]. 2 FEATHERWEIGHT GO BY EXAMPLE Formally, FG and FGG are tiny languages, containing only structures, interfaces, and methods. Our examples use features of Go missing in FG and FGG, including booleans, integers, strings, and variable bindings. We show how to declare booleans in FG and FGG in [Griesemer et al. 2020]. 2.1 FG by example Functions in FG. Figure 1 shows higher-order functions in FG. Interface Any has no methods, and so is implemented by any type. Interface Function has a single method, Apply(x Any) Any, which has an argument and result of type Any. It is implemented by any structure that defines a method with the same name and same signature. In Go structures and methods are declared separately, as compared to Java where they are grouped together in a class declaration. We give three examples. Structure incr has a single field, n, of type int. Its Apply method has receiver this of type incr, argument x of type Any, and result type Any, and increments its argument by n. You might expect the argument and result to instead have type int, but then the declared method would not implement the Function interface, because the method name and signature must match exactly. In the method’s body, x.(int) is a type assertion that checks its argument is an integer; otherwise it panics, which is Go jargon for raising a runtime error. A structure is created by a literal, consisting of the structure name and its field values in braces. For instance, incr{-5}.Apply(3) returns -2. A field of a structure is accessed in the usual way, this.n. Here this is a variable bound to the receiver, not a keyword. Structure pos has no fields, and its apply method returns true if given a positive integer. Structure compose has two fields, each of which is a function, and its apply method applies the two in succession. The top-level main method composes incr{-5} with pos{} and applies it to 3, yielding false. One cannot pass a value of type Anywhere a boolean is expected, so the type assertion .(bool) is required. Bound variable names are irrelevant when comparing method signatures, but method names and type names must match exactly. For example, the following signatures are considered equivalent: Apply(x Any) Any Apply(arg Any) Any Equality in FG. Figure 2 shows equality in FG. Interface Eq has one method with signature Equal(that Eq) bool. If a type implements this interface we say it supports equality. A type declaration introduces Int as a synonym for integers, and a method declaration ensures that type supports equality. Since signatures must match exactly, in the method the argument has type Eq and the body uses a type assertion to convert it to Int as required. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:5 type Any interface {} type Function interface { Apply(x Any) Any } type incr struct { n int } func (this incr) Apply(x Any) Any { return x.(int) + this.n } type pos struct {} func (this pos) Apply(x Any) Any { return x.(int) > 0 } type compose struct { f Function g Function } func (this compose) Apply(x Any) Any { return this.g.Apply(this.f.Apply(x)) } func main() { var f Function = compose{incr{-5},pos{}} var _ bool = f.Apply(3).(bool) // false } Fig. 1. Functions in FG type Eq interface { Equal(that Eq) bool } type Int int func (this Int) Equal(that Eq) bool { return this == that.(Int) } type Pair struct { left Eq right Eq } func (this Pair) Equal(that Eq) bool { return this.left.Equal(that.(Pair).left) && this.right.Equal(that.(Pair).right) } func main() { var i, j Int = 1, 2 var p Pair = Pair{i, j} var _ bool = p.Equal(p) // true } Fig. 2. Equality in FG type List interface { Map(f Function) List } type Nil struct {} type Cons struct { head Any tail List } func (xs Nil) Map(f Function) List { return Nil{} } func (xs Cons) Map(f Function) List { return Cons{f.Apply(xs.head), xs.tail.Map(f)} } func main() { var xs List = Cons{3, Cons{6, Nil{}}} var ys List = xs.Map(incr{-5}) // Cons{-2, Cons{1, Nil{}}} var _ List = ys.Map(pos{}) // Cons{false, Cons{true, Nil{}}} } Fig. 3. Lists in FG A second type declaration introduces the structure Pair with two fields left and right which may be of any type that supports equality, and the method declaration ensures that pairs themselves support equality. Again, the argument has type Eq and the body uses a type assertion to convert it to a Pair as required. The top level main method builds a pair of integers and compares it to itself for equality, yielding true. Since pairs are to support equality, their components are also required to support equality. In general, if a structure is to satisfy some interface we may need to require that each field of that structure satisfies the same interface—a property we refer to as type pollution. An alternative design Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:6 Griesemer et al. would give fields the type Any, and to replace this.left by this.left.(Eq), and similarly for the other component. The alternative design is more flexible—it permits fields of the pair to have any type—but is less efficient (the type assertions must be checked at runtime) and less reliable (the type assertions may fail). As we will see, FGG will let us avoid type pollution, providing flexibility, efficiency, and reliability all at the same time. Lists in FG. Figure 3 shows lists in FG. Interface List has a single method: Map(f Function) List, which applies its argument to each element of its receiver. We define two structures that implement the list interface. Structure Nil has no fields, while structure Cons has two fields, a head of any type and a tail which is a list. The methods to define Map are straightforward, and the main method shows an example of its use. Go is designed to enable efficient implementation. Structures are laid out in memory as a sequence of fields, while an interface is a pair of a pointer to an underlying structure and a pointer to a dictionary of methods. To ensure the layout of a structure is finite, a structure that recurses on itself is forbidden. Thus, the declaration type Bad struct { oops Bad } is not allowed, and similarly for mutual recursion. However, structures that recurse through an interface are permitted, such as type Cons struct { head Any; tail List } where the tail field of type List may itself contain a Cons, since Cons implements interface List. 2.2 FGG by Example We now adapt the examples of the previous section to generics. Functions in FGG. Figure 4 shows higher-order functions in FGG. The interface for functions now takes two type parameters, Function(type a Any, b Any). Each type parameter is followed by an interface it must implement, called its bound. Here the bounds indicate that the argument and result may be of any type. The signature for the method is now Apply(x a) b, where the first type parameter is the argument type and the second the result type. Structures incr and pos are as before. However, they now have more natural signatures for their apply methods, where all occurrences of Any are replaced by int or bool as appropriate. Type assertions in the method bodies are no longer needed, and the types ensure a panic never occurs. The structure for composition now takes three type parameters. In the main method, type param- eters are added and the type assertion at the end is no longer required. Equality in FGG. Figure 5 shows equality in FGG. The interface for equality is now written Eq(type a Eq(a)). It accepts a type parameter a where the bound is itself Eq(a). The method has signature Equal(that a) bool. The situation where a type parameter appears in its own bound is known as F-bounded polymorphism [Canning et al. 1989], and a similar idiom is common in Java with generics [Bracha et al. 1998; Naftalin and Wadler 2006]. Since we use a type parameter for the argument to Equal, in the method declaration for Int the argument must now have type Int instead of type Eq. A type assertion in the method body is no longer required, increasing efficiency and reliability. The type declaration for Pair now take two type parameters, a and b, which are both bounded by Any. The method declaration for equality on pairs also uses two type parameters, a and b, bounded by Eq(a) and Eq(b) respectively, so the call to Equality on the components of the pair is permitted. Crucially, FGG permits the bounds on the type parameter in a receiver to implement the bound on the type parameter in the corresponding structure declaration (receiver type parameters are Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:7 type Function(type a Any, b Any) interface { Apply(x a) b } type incr struct { n int } func (this incr) Apply(x int) int { return x + this.n } type pos struct {} func (this pos) Apply(x int) bool { return x > 0 } type compose(type a Any, b Any, c Any) struct { f Function(a, b) g Function(b, c) } func (this compose(type a Any, b Any, c Any)) Apply(x a) c { return this.g.Apply(this.f.Apply(x)) } func main() { var f Function(int,bool) = compose(int,int,bool){incr{-5},pos{}} var _ bool = f.Apply(3) // false } Fig. 4. Functions in FGG type Eq(type a Eq(a)) interface { Equal(that a) bool } type Int int func (this Int) Equal(that Int) bool { return this == that } type Pair(type a Any, b Any) struct { left a right b } func (this Pair(type a Eq(a), b Eq(b))) Equal(that Pair(a,b)) bool { return this.left.Equal(that.left) && this.right.Equal(that.right) } func main() { var i, j Int = 1, 2 var p Pair(Int, Int) = Pair(Int, Int){i, j} var _ bool = p.Equal(p) // true } Fig. 5. Equality in FGG type List(type a Any) interface { Map(type b Any)(f Function(a, b)) List(b) } type Nil(type a Any) struct {} type Cons(type a Any) struct { head a tail List(a) } func (xs Nil(type a Any)) Map(type b Any)(f Function(a,b)) List(b) { return Nil(b){} } func (xs Cons(type a Any)) Map(type b Any)(f Function(a,b)) List(b) { return Cons(b) {f.Apply(xs.head), xs.tail.Map(b)(f)} } func main() { var xs List(int) = Cons(int){3, Cons(int){6, Nil(int){}}} var ys List(int) = xs.Map(int)(incr{-5}) var _ List(bool) = ys.Map(bool)(pos{}) } Fig. 6. Lists in FGG type Edge(type e Edge(e,v), v Vertex(e,v)) interface { Source() v Target() v } type Vertex(e Edge(e,v), v Vertex(e,v)) interface { Edges() List(e) } Fig. 7. Mutually recursive bounds Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:8 Griesemer et al. covariant). This is in contrast to method signatures, which must exactly match the signature in the interface (signatures are nonvariant). In this case, the bounds on a and b in the type declaration for pairs are both Any, while the bounds on a and b in the receiver are Eq(a) and Eq(b). By covariance, since Eq(a) and Eq(b) implement Any, the method declaration is allowed. The Go team’s current design does not support covariant receiver typing. Instead, receivers are nonvariant, just like method signatures. With that design, the bounds on a and b in the declaration for pairs must exactly match those in the method receiver. Either the type and method declarations must both use bounds Eq(a) and Eq(b) (in which case one cannot have pairs where the components do not support equality, even if don’t need that pair to itself support equality, reintroducing type pollution and reducing flexibility), or they must both use bounds Any (in which case the method body will need to add type assertions to Eq(a) and Eq(b), reducing efficiency and reliability). Lists in FGG. Figure 6 shows lists in FGG. Interface List now takes as a parameter the type of the elements of the list, bounded by Any. The signature for the map method is now Map(type b Any)(f Function(a, b)) List(b). The list interface takes a parameter a for the type of elements of the receiver list, while the map method itself takes an additional parameter b for the type of elements of the result list. The two structures that implement lists now also take a type parameter, again bounded by Any. It may seem odd that Nil requires a parameter, since it represents a list with no elements. However, without this parameter we could not declare that Nil has method Map, whose signature mentions the type of the list elements. The main method simply adds type parameters. In Go, no name can be bound to both a value and a type in a given scope, so it is always unambiguous as to whether one is parsing a type or an expression. In practice, writing out all type parameters in full can be tedious, and generic Go permits such parameters to be omitted when they can be inferred. Here we always require type parameters, leaving inference for future work. Type parameter names and variable names are irrelevant when comparing method signatures, but method names, bounds on type parameters, and type names must match exactly. For example, the following two signatures are considered equivalent: Map(type b Any)(f Function(a, b)) List(b) Map(type bob Any)(fred Function(a, bob)) List(bob) If we wanted to define equality on lists without type assertions, we would need to bound the elements of the list so that they support equality, changing every occurrence of (type a Any), in the code to (type a Eq(a)), and similarly for b in the signature of Map. This is a form of type pollution. An alternative design that avoids pollution, based on our solution to The Expression Problem, can be found in [Griesemer et al. 2020]. Mutual recursion in type bounds. In a declaration that introduces a list of type parameters, the bounds of each may refer to any of the others. Figure 7 shows two mutually-recursive interface declarations that may be useful in representing graphs. It is parameterised over types for edges and vertexes. Each edge has a source and target vertex, while each vertex has a list of edges. 2.3 The Expression Problem Following Wadler [1998], we present The Expression Problem pared to a minimum. Our solution appears in Figure 8. There are just two structures that construct expressions, Num and Plus, which denote numbers and the sum of two expressions, respectively; and two methods that operate on expressions, Eval and String, which evaluate an expression and convert it to a string, respectively. We show that each constructor and operation can be added independently, proceeding in four steps: Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:9 // Eval on Num type Evaler interface { Eval() int } type Num struct { value int } func (e Num) Eval() int { return e.value } // Eval on Plus type Plus(type a Any) struct { left a right a } func (e Plus(type a Evaler)) Eval() int { return e.left.Eval() + e.right.Eval() } // String on Num type Stringer interface { String() string } func (e Num) String() string { return fmt.Sprintf("%d", e.value) } // String on Plus func (e Plus(type a Stringer)) String() string { return fmt.Sprintf("(%s+%s)", e.left.String(), e.right.String()) } // tie it all together type Expr interface { Evaler Stringer } func main() { var e Expr = Plus(Expr){Num{1}, Num{2}} var _ Int = e.Eval() // 3 var _ string = e.String() // "(1+2)" } Fig. 8. Expression problem in FGG (1) define Eval on Num (3) define String on Num (2) define Eval on Plus (4) define String on Plus The order of steps 2 and 3 can be reversed: we may extend either by adding a new constructor or a new operation. We assume availability of the library function fmt.Sprintf to format strings. Structure Num contains an integer value. Structure Plus contains two fields, left and right, which are themselves expressions. Typically, we might expect the type of these fields to be an interface specifying all operations we wish to perform on expressions. But the whole point of the expression problem is that we may add other operations later! Thus, plus takes a type parameter for the type of these fields. We bound the type parameter with Any, permitting it to be instantiated by any type. For each operation, Eval and String, we define a corresponding interface to specify that operation, Evaler and Stringer. (The naming convention is typical of Go.) When defining Eval on Plus, the receiver’s type parameter is bounded by interface Evaler, allowing Eval to be recursively invoked on the left and right expressions. Similarly, when defining String on Plus, the receiver’s type parameter is bounded by interface Stringer, allowing String to be recursively invoked. Note this depends crucially on FGG’s support for covariant receivers in method declarations. Since the bounds or the receivers in the method declarations, Evaler and Stringer, implement the bounds in the type declarations, Any, the method declarations are allowed. A last step shows how to tie it all together. We define an interface Expr embedding Evaler and Stringer, and show how to build an Expr value which we can both evaluate and convert to a string. How close could we get without generics? If we know all operations in advance, then in place of the type parameter in Plus we can use interface Expr, defining all required operations; but that violates the requirement that we can add operations later. Alternatively, in place of the type parameter in Plus we can use interface Any, with type assertions to Evaler or Stringer before the Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:10 Griesemer et al. type Top struct {} type Function interface { Apply<0> Top Apply(x int) int } type incr struct { n int } func (this incr) Apply<0> Top { return Top{} } func (this incr) Apply(x int) int { return x + this.n } type Function interface { Apply<1> Top Apply(x int) bool } type pos struct {} func (this pos) Apply<1> Top { return Top{} } func (this pos) Apply(x int) bool { return x > 0 } type List interface { Map<2>() Top Map(f Function) List Map(f Function) List } type Nil struct {} type Cons struct { head int tail List } func (xs Nil) Map<2>() Top { return Top{} } func (xs Cons) Map<2>() Top { return Top{} } func (xs Nil) Map(f Function) List { return Nil{} } func (xs Cons) Map(f Function) List { return Cons {f.Apply(xs.head), xs.tail.Map(f)} } func (xs Nil) Map(f Function) List { return Nil{} } func (xs Cons) Map(f Function) List { return Cons {f.Apply(xs.head), xs.tail.Map(f)} } type List interface { Map<3>() Top } type Nil struct {} type Cons struct { head bool tail List } func (xs Nil) Map<3>() Top { return Top{} } func (xs Cons) Map<3>() Top { return Top{} } func main() { var xs List = Cons{3, Cons{6, Nil}} var ys List = xs.Map(incr{-5}) var _ List = ys.Map(pos{}) } Fig. 9. Monomorphisation: example of FG translation type Box(type a Any) struct { value a } func (this Box(type a Any)) Nest(n int) Any { if (n == 0) { return this } else { return Box(Box(a)){this}.Nest(n-1) } } Fig. 10. FGG code that cannot be monomorphised recursive calls; that allows us to add operations later, but violates the requirement that all types be checked statically. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:11 2.4 Monomorphisation by example We translate FGG into FG viamonomorphisation. As an example, consider the FGG code in Figures 4 and 6. We only include the code relevant to the main method, so omit composition and equality. The given code monomorphises to the FG program shown in Figure 9. Each parametric type and method in FGG is translated to a family of types and methods in FG, one for each possible instantiation of the type parameters. FGG type List(a) is instantiated at types int and bool, so it translates to the two FG types List and List. For convenience, we assume that angle brackets and commas “<,>” may appear in FG identifiers, although that is not allowed in Go. In our prototype, we use Unicode letters that resemble angle brackets and a dash: Canadian Syllabics Pa (U+1438), Po (U+1433), and Final Short Horizontal Stroke (U+1428). Monomorphisation tracks for each method the possible types of its receiver and type parameters. In this particular program, we need two instances of Map over lists of integers, one that yields a list of integers and one that yields a list of booleans, and none for Map over lists of booleans. Each interface also contains an instance of a dummy version of Apply or Map, here called, e.g., Map<2>, where the number in brackets stands for a hash computed from the method signature. A dummy method is provided for every source FGG method; these dummy methods are needed to ensure a correct implementation relation between structures and interfaces is maintained at runtime. For instance, if f is bound to incr{1} then the type assertion f.(List) should fail; but without the dummy, interface List would have no methods and hence any structure or interface would implement it. Monomorphisation yields specialised type declarations for structures and interfaces, and spe- cialised method declarations, plus the required dummymethods. The source FGG and its translation to FG are both well-typed, and both evaluate to corresponding terms: we will show the translation preserves typing and is a bisimulation. Not all typable FGG source can be monomorphised. Figure 10 shows a program that exhibits polymorphic recursion, where a method called at one type recursively calls itself at a different type. Here, calling method Nest on a receiver of type Box(a) leads to a recursive call on a receiver of type Box(Box(a)). Monomorphisation is impossible because we cannot determine in advance to what depth the types will nest. We will present a theorem stating that if source code does not exhibit problematic polymorphic recursion then it can be monomorphised. 3 FEATHERWEIGHT GO 3.1 FG syntax Figure 11 presents FG syntax. We let 𝑓 range over field names,𝑚 range over method names, 𝑥 range over variable names, 𝑡𝑆 , 𝑢𝑆 range over structure names, and 𝑡𝐼 , 𝑢𝐼 range over interface names. We let 𝑡,𝑢 range over type names, which are either structure or interface type names. We let 𝑑 and 𝑒 range over expressions, which have five forms: variable 𝑥 , method call 𝑒.𝑚(𝑒), structure literal 𝑡𝑆{𝑒}, selection 𝑒.𝑓 , and type assertion 𝑒.(𝑡). By convention, 𝑒 stands for the sequence 𝑒1, . . . , 𝑒𝑛 . We consider 𝑒 and 𝑒 to be distinct metavariables. A method signature𝑀 has the form (𝑥 𝑡) 𝑡 . Here 𝑥 stands for 𝑥1, . . . , 𝑥𝑛 and 𝑡 stands for 𝑡1, . . . , 𝑡𝑛 , and hence 𝑥 𝑡 stands for 𝑥1 𝑡1, . . . , 𝑥𝑛 𝑡𝑛 . We use similar conventions extensively. A method specification 𝑆 is a method name followed by a method signature. A type literal 𝑇 is either a structure struct {𝑓 𝑡} or an interface interface {𝑆}. A declaration 𝐷 is either a type declaration type 𝑡 𝑇 or a method declaration func (𝑥 𝑡𝑆 ) 𝑚𝑀 {return 𝑒}. In our examples, interface declarationsmay contain interface embeddings, i.e., a reference to another interface; for our formalism, we assume these are always expanded out to the corresponding method specifications. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:12 Griesemer et al. Field name 𝑓 Method name 𝑚 Variable name 𝑥 Structure type name 𝑡𝑆 , 𝑢𝑆 Interface type name 𝑡𝐼 , 𝑢𝐼 Type name 𝑡,𝑢 ::= 𝑡𝑆 | 𝑡𝐼 Method signature 𝑀 ::= (𝑥 𝑡) 𝑡 Method specification 𝑆 ::=𝑚𝑀 Type Literal 𝑇 ::= Structure struct {𝑓 𝑡} Interface interface {𝑆} Declaration 𝐷 ::= Type declaration type 𝑡 𝑇 Method declaration func (𝑥 𝑡𝑆 ) 𝑚𝑀 {return 𝑒} Program 𝑃 ::= package main; 𝐷 func main() {_ = 𝑒} Expression 𝑑, 𝑒 ::= Variable 𝑥 Method call 𝑒.𝑚(𝑒) Structure literal 𝑡𝑆{𝑒} Select 𝑒.𝑓 Type assertion 𝑒.(𝑡) Fig. 11. FG syntax A program 𝑃 consists of a sequence of declarations 𝐷 and a top-level expression 𝑒 , written in the stylised form shown in the figure to make it legal Go. We sometimes abbreviate it as 𝐷 ▷ 𝑒 . 3.2 Auxiliary functions Figure 12 presents several auxiliary definitions. All definitions assume a given program with a fixed sequence of declarations 𝐷 . Function fields(𝑡𝑆 ) looks up the structure declaration for 𝑡𝑆 and returns a sequence (𝑓 𝑡) of field names and their types. Write 𝑡𝑆 .𝑚 to refer to the method declaration with receiver type 𝑡𝑆 and name𝑚. Function body(𝑡𝑆 .𝑚) returns (𝑥 : 𝑡𝑆 , 𝑥 : 𝑡).𝑒 , where 𝑥 : 𝑡𝑆 is the receiver parameter and its type, 𝑥 : 𝑡 the argument parameters and their types, and 𝑒 the body from the declaration of a method with receiver of type 𝑡𝑆 and name𝑚. In the phrase func (𝑥 𝑡𝑆 )𝑚(𝑥 𝑡) 𝑡 , each of 𝑡𝑆 , 𝑡 , and 𝑡 is considered a distinct metavariable, and similarly for 𝑥 and 𝑥 . Function type(𝑣) is explained in Section 3.4. Predicate unique(𝑆) holds if for every method specification𝑚𝑀 in 𝑆 the method name𝑚 uniquely determines the method signature𝑀 . Function tdecls(𝐷) returns a sequence with the name of every type declared in 𝐷 . Function mdecls(𝐷) returns a sequence with a pair 𝑡𝑆 .𝑚 for every method declared in 𝐷 . Predicate distinct, not defined in the figure, takes a sequence, and holds if no item in the sequence is duplicated. We are careful to distinguish between sets and sequences. Predicate distinct must take a sequence rather than a set, because items in a set are distinct by definition. Sequences may implicitly coerce to sets, but not vice-versa. When comparing method signatures, the names of formal parameters are ignored; signatures are considered equal if they contain the same types in the same sequence. Function methods(𝑡) returns the set of all method specifications belonging to type 𝑡 . If 𝑡 is a structure type the method specifications are those from the method declarations with a receiver of the given type. It 𝑡 is an interface type, the method specifications are those given in the interface. Figure 13 presents the FG typing rules. Let Γ range over environments, which are sequences of variables paired with type names, 𝑥 : 𝑡 . We write ∅ for the empty environment. Judgement 𝑡 <: 𝑢 holds if type 𝑡 implements type 𝑢. A structure type 𝑡𝑆 is only implemented by itself, while type 𝑡 implements interface type 𝑡𝐼 if the methods defined on 𝑡 are a superset of those defined on 𝑡𝐼 . It follows from the definition that <: is reflexive and transitive. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:13 (type 𝑡𝑆 struct{𝑓 𝑡}) ∈ 𝐷 fields(𝑡𝑆 ) = 𝑓 𝑡 (func (𝑥 𝑡𝑆 ) 𝑚(𝑥 𝑡) 𝑡 {return 𝑒}) ∈ 𝐷 body(𝑡𝑆 .𝑚) = (𝑥 : 𝑡𝑆 , 𝑥 : 𝑡).𝑒 type(𝑡𝑆{𝑣}) = 𝑡𝑆 𝑚𝑀1,𝑚𝑀2 ∈ 𝑆 implies𝑀1 = 𝑀2 unique(𝑆) tdecls(𝐷) = [𝑡 | (type 𝑡 𝑇 ) ∈ 𝐷] mdecls(𝐷) = [𝑡𝑆 .𝑚 | (func (𝑥 𝑡𝑆 ) 𝑚𝑀 {return 𝑒}) ∈ 𝐷] methods(𝑡𝑆 ) = {𝑚𝑀 | (func (𝑥 𝑡𝑆 ) 𝑚𝑀 {return 𝑒}) ∈ 𝐷} type 𝑡𝐼 interface{𝑆} ∈ 𝐷 methods(𝑡𝐼 ) = 𝑆 Fig. 12. FG auxiliary functions 3.3 FG Typing We write ok to indicate a construct is well-formed. Judgement 𝑡 ok holds if type 𝑡 is declared. Judgement 𝑆 ok holds if method specification 𝑆 is well formed: all formal parameters 𝑥 in it are distinct, and all the types 𝑡, 𝑡 in it are declared. Judgement𝑇 ok holds if type literal𝑇 is well formed: for a structure, all field names must be distinct and all types declared; for an interface, all its method specifications must be well formed. Judgement 𝐷 ok holds if declaration 𝐷 is well formed: for a type declaration, its type literal must be well formed; for a method declaration, its receiver and formal parameters must be distinct, all types must be declared, the method body must be well typed in the appropriate environment, and the expression type must implement the declared return type. Judgement Γ ⊢ 𝑒 : 𝑡 holds if in environment Γ expression 𝑒 has type 𝑡 . Rules for variable, method call, structure literal, and field selection are straightforward. For instance, the four hypotheses for a method call check the type of the receiver, check the types of the arguments, look up the signature of the method, and confirm the types of the arguments implement the types of the parameters. Type assertions are more interesting. A type assertion 𝑒.(𝑡) always returns a value of type 𝑡 (if it doesn’t panic). Let 𝑒 have type 𝑢. There are three cases. If 𝑢 and 𝑡 are both interface types (t-assert𝐼 ) then the assertion is always allowed, since 𝑒 could always conceivably evaluate to a structure that implements 𝑡 . If 𝑢 is an interface but 𝑡 is a structure (t-assert𝑆 ) then the assertion is allowed only if 𝑡 implements 𝑢, since otherwise 𝑒 could not possibly contain a structure of type 𝑡 . If 𝑢 is a structure type (t-stupid) then it is stupid to write the assertion in source code, since the assertion could be checked at compile time, making it pointless. Nonetheless, during reduction a variable of interface type will be replaced by a value of structure type, so without such stupid type assertions an expression would become ill-typed during reduction. We write a box around this rule to indicate that it doesn’t apply to source terms, but may apply to terms that result from reducing the source. Stupid type assertions are similar to stupid casts as found in Featherweight Java. Judgement 𝑃 ok holds if program 𝑃 is well formed: all its type declarations are distinct, all its method declarations are distinct (each pair of a receiver type with a method name is distinct), all its declarations are well formed, and its body is well typed in the empty environment. 3.4 FG Reduction Figure 14 presents the FG reduction rules. A value 𝑣 is a structure literal 𝑡𝑆{𝑣} where each field is itself filled with a value. The auxiliary function type(𝑣) returns 𝑡𝑆 when 𝑣 = 𝑡𝑆{𝑣}. Evaluation contexts 𝐸 are defined in the usual way. Judgement 𝑑 −→ 𝑒 holds if expression 𝑑 steps to expression Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:14 Griesemer et al. Implements, well-formed type 𝑡 <: 𝑢 𝑡 ok <:𝑆 𝑡𝑆 <: 𝑡𝑆 <:𝐼 methods(𝑡) ⊇ methods(𝑡𝐼 ) 𝑡 <: 𝑡𝐼 t-named (type 𝑡 𝑇 ) ∈ 𝐷 𝑡 ok Well-formed method specifications and type literals 𝑆 ok 𝑇 ok t-specification distinct (𝑥) 𝑡 ok 𝑡 ok 𝑚(𝑥 𝑡) 𝑡 ok t-struct distinct (𝑓 ) 𝑡 ok struct {𝑓 𝑡} ok t-interface unique(𝑆) 𝑆 ok interface {𝑆} ok Well-formed declarations 𝐷 ok t-type 𝑇 ok type 𝑡 𝑇 ok t-func distinct (𝑥, 𝑥) 𝑡𝑆 ok 𝑡 ok 𝑢 ok 𝑥 : 𝑡𝑆 , 𝑥 : 𝑡 ⊢ 𝑒 : 𝑡 𝑡 <: 𝑢 func (𝑥 𝑡𝑆 ) 𝑚(𝑥 𝑡) 𝑢 {return 𝑒} ok Expressions Γ ⊢ 𝑒 : 𝑡 t-var (𝑥 : 𝑡) ∈ Γ Γ ⊢ 𝑥 : 𝑡 t-call Γ ⊢ 𝑒 : 𝑡 Γ ⊢ 𝑒 : 𝑡 (𝑚(𝑥 𝑢) 𝑢) ∈ methods(𝑡) 𝑡 <: 𝑢 Γ ⊢ 𝑒.𝑚(𝑒) : 𝑢 t-literal 𝑡𝑆 ok Γ ⊢ 𝑒 : 𝑡 (𝑓 𝑢) = fields(𝑡𝑆 ) 𝑡 <: 𝑢 Γ ⊢ 𝑡𝑆{𝑒} : 𝑡𝑆 t-field Γ ⊢ 𝑒 : 𝑡𝑆 (𝑓 𝑢) = fields(𝑡𝑆 ) Γ ⊢ 𝑒.𝑓𝑖 : 𝑢𝑖 t-assert𝐼 𝑡𝐼 ok Γ ⊢ 𝑒 : 𝑢𝐼 Γ ⊢ 𝑒.(𝑡𝐼 ) : 𝑡𝐼 t-assert𝑆 𝑡𝑆 ok Γ ⊢ 𝑒 : 𝑢𝐼 𝑡𝑆 <: 𝑢𝐼 Γ ⊢ 𝑒.(𝑡𝑆 ) : 𝑡𝑆 t-stupid 𝑡 ok Γ ⊢ 𝑒 : 𝑢𝑆 Γ ⊢ 𝑒.(𝑡) : 𝑡 Programs 𝑃 ok t-prog distinct (tdecls(𝐷)) distinct (mdecls(𝐷)) 𝐷 ok ∅ ⊢ 𝑒 : 𝑡 package main; 𝐷 func main() {_ = 𝑒} ok Fig. 13. FG typing 𝑒 . There are four rules, for field selection, method call, type assertion, and closure under evaluation contexts. All are straightforward. 3.5 FG properties We have the usual results relating typing and reduction. Lemma 3.1 (Well formed). If Γ ⊢ 𝑒 : 𝑡 then 𝑡 ok. The substitution lemma is straightforward. It is sufficient to consider empty environments for the substituted terms, since FG has no binding constructs (such as lambda) in expressions. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:15 Value 𝑣 ::= 𝑡𝑆{𝑣} Evaluation context 𝐸 ::= Hole □ Method call receiver 𝐸.𝑚(𝑒) Method call arguments 𝑣 .𝑚(𝑣, 𝐸, 𝑒) Structure 𝑡𝑆{𝑣, 𝐸, 𝑒} Select 𝐸.𝑓 Type assertion 𝐸.(𝑡) Reduction 𝑑 −→ 𝑒 r-field (𝑓 𝑡) = fields(𝑡𝑆 ) 𝑡𝑆{𝑣}.𝑓𝑖 −→ 𝑣𝑖 r-call (𝑥 : 𝑡𝑆 , 𝑥 : 𝑡).𝑒 = body(type(𝑣).𝑚) 𝑣 .𝑚(𝑣) −→ 𝑒 [𝑥 := 𝑣, 𝑥 := 𝑣] r-assert type(𝑣) <: 𝑡 𝑣 .(𝑡) −→ 𝑣 r-context 𝑑 −→ 𝑒 𝐸 [𝑑] −→ 𝐸 [𝑒] Fig. 14. FG reduction Lemma 3.2 (Substitution). If ∅ ⊢ 𝑣 : 𝑡 and 𝑥 : 𝑢 ⊢ 𝑒 : 𝑢 and 𝑡 <: 𝑢 then ∅ ⊢ 𝑒 [𝑥 := 𝑣] : 𝑡 for some type 𝑡 with 𝑡 <: 𝑢. The following are straightforward adaptions of the usual results. We say expression 𝑒 panics if there exist evaluation context 𝐸, value 𝑣 , and type 𝑡 such that 𝑒 = 𝐸 [𝑣 .(𝑡)] and type(𝑣) ≮: 𝑡 . Theorem 3.3 (Preservation). If ∅ ⊢ 𝑑 : 𝑢 and 𝑑 −→ 𝑒 then ∅ ⊢ 𝑒 : 𝑡 for some 𝑡 with 𝑡 <: 𝑢. Theorem 3.4 (Progress). If ∅ ⊢ 𝑑 : 𝑢 then either 𝑑 is a value, 𝑑 −→ 𝑒 for some 𝑒 , or 𝑑 panics. 4 FEATHERWEIGHT GENERIC GO 4.1 FGG Syntax Figure 15 presents FGG syntax, with the differences from FG syntax highlighted. We let 𝛼 range over type parameters and let 𝜏, 𝜎 range over types. A type is either a type parameter 𝛼 or a named type 𝑡 (𝜏). We also let 𝜏𝑆 , 𝜎𝑆 range over structure types of the form 𝑡𝑆 (𝜏); 𝜏𝐼 , 𝜎𝐼 range over interface types of the form 𝑡𝐼 (𝜏); and, 𝜏𝐽 , 𝜎𝐽 range over types that are either type parameters or interfaces 𝜏𝐼 . Expressions and declarations are updated to replace type names by types, and method calls are updated to include type parameters: a structure declaration is now struct {𝑓 𝜏} and a method call is now 𝑒.𝑚(𝜏) (𝑒), a structure literal is now 𝜏𝑆{𝑒}, and a type assertion is now 𝑒.(𝜏). We let Φ,Ψ range over type formals, which have the form type 𝛼 𝜏𝐼 , pairing type parameters with their bounds, which are interface types. The bounds in type formals are mutually recursive, i.e., each interface in 𝜏𝐼 may refer to any parameter in 𝛼 . Type declarations type 𝑡 (Φ) 𝑇 , and signatures (Ψ) (𝑥 𝜏) 𝜏 , and method declarations func (𝑥 𝑡𝑆 (Φ)) 𝑚𝑀 {return 𝑒} now include type formals. We let 𝜙,𝜓 range over type actuals, which are sequences of types. 4.2 Auxiliary functions Figure 16 presents several auxiliary definitions. As before, Γ ranges over environments, which are now sequences that pair variables with types, 𝑥 : 𝜏 . In addition, Δ ranges over type environments, which are sequences that pair type parameters with bounds, 𝛼 : 𝜏𝐼 . Type formals Φ,Ψmay implicitly coerce to type environments. We write 𝜂 = (Φ := 𝜙) for the substitution of formals Φ by actuals 𝜙 , and 𝜂 = (Φ :=Δ 𝜙) for the partial function that also checks that 𝜙 respects the bounds imposed by Φ. If a partial function that is undefined appears in the hypothesis of a rule, then the corresponding premise does not hold. We write Φˆ for the type parameters of Φ. Functions fields(𝜏𝑆 ) and body(𝜏𝑆 .𝑚(𝜓 )) are updated to replace type names by types, and for the latter to include method type arguments. The definitions are adjusted to include type formals Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:16 Griesemer et al. Field name 𝑓 Method name 𝑚 Variable name 𝑥 Structure type name 𝑡𝑆 , 𝑢𝑆 Interface type name 𝑡𝐼 , 𝑢𝐼 Type name 𝑡,𝑢 ::= 𝑡𝑆 | 𝑡𝐼 Type parameter 𝛼 Method signature 𝑀 ::= (Ψ) (𝑥 𝜏) 𝜏 Method specification 𝑆 ::=𝑚𝑀 Type Literal 𝑇 ::= Structure struct {𝑓 𝜏} Interface interface {𝑆} Declaration 𝐷 ::= Type declaration type 𝑡 (Φ) 𝑇 Method declaration func (𝑥 𝑡𝑆 (Φ)) 𝑚𝑀 {return 𝑒} Program 𝑃 ::= package main; 𝐷 func main() {_ = 𝑒} Type 𝜏, 𝜎 ::= Type parameter 𝛼 Named type 𝑡 (𝜏) Structure type 𝜏𝑆 , 𝜎𝑆 ::= 𝑡𝑆 (𝜏) Interface type 𝜏𝐼 , 𝜎𝐼 ::= 𝑡𝐼 (𝜏) Interface-like type 𝜏𝐽 , 𝜎𝐽 ::= 𝛼 | 𝜏𝐼 Type formal Φ, Ψ ::= type 𝛼 𝜏𝐼 Type actual 𝜙 ,𝜓 ::= 𝜏 Expression 𝑒 ::= Variable 𝑥 Method call 𝑒.𝑚(𝜏) (𝑒) Structure literal 𝜏𝑆{𝑒} Select 𝑒.𝑓 Type assertion 𝑒.(𝜏) Fig. 15. FGG syntax which are instantiated appropriately. Functions type, tdecls, and mdecls and predicate unique are updated to replace type names by types and to include type formals. Function boundsΔ (𝜏) takes a type parameter to its bound, and leaves its argument unchanged otherwise. Function methodsΔ (𝜏) is updated to accept a type environment and to replace type names by types. The definition is adjusted to include type formals which are instantiated appropriately. If methods is applied to a type parameter, that parameter behaves the same as its bounding interface, so methodsΔ (𝜏) = methodsΔ (boundsΔ (𝜏)) for all 𝜏 . 4.3 FGG Typing Figure 17 presents the FGG typing rules. Judgement Δ ⊢ 𝜏 <:𝜎 now depends on a type environment and relates types rather than type names. The definition is adjusted so that a type parameter implements its bound. It still follows from the definition that <: is reflexive and transitive. Judgement Φ <: Ψ compares the corresponding bounds of two type formals under an empty type environment. Judgement Δ ⊢ 𝜏 ok holds if a type is well formed: all type parameters in it must be declared in Δ and all named types must be instantiated with type arguments that satisfy the bounds of the corresponding type parameters. Judgement Δ ⊢ 𝜙 ok holds if under environment Δ all types in 𝜙 are well formed. Judgement Φ ⊢ Ψ ok holds if under type environment Φ the type formal Ψ is well formed: all type parameters bound by Φ and Ψ are distinct, and all the bounds in Ψ are well formed in the type environment that results from combining Φ and Ψ. Note this permits mutually recursive bounds in a type formal. Judgement Φ; Ψ ok Δ holds if a method declaration with receiver formals Φ and method formals Ψ is well formed, yielding type environment Δ: it requires that Φ is well formed under the empty environment, Ψ is well formed under Φ, and Δ is the concatenation of Φ and Ψ. Hence, the type formals of the receiver are in scope when declaring the type formals of the method, but not vice versa, and both are in scope for declaring the types of the arguments and result. Judgement Φ ⊢ 𝑆 ok holds if under type environment Φ method specification 𝑆 is well formed: it requires Φ,Ψ ok Δ where Ψ is the type formals of the method specification, and the rest is similar Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:17 (type 𝛼 𝜏𝐼 ) = Φ 𝜂 = (𝛼 := 𝜏) (Φ := 𝜏) = 𝜂 (type 𝛼 𝜏𝐼 ) = Φ 𝜂 = (Φ := 𝜙) Δ ⊢ (𝛼 <: 𝜏𝐼 ) [𝜂] (Φ :=Δ 𝜙) = 𝜂 (type 𝑡𝑆 (Φ) struct {𝑓 𝜏}) ∈ 𝐷 𝜂 = (Φ := 𝜙) fields(𝑡𝑆 (𝜙)) = (𝑓 𝜏) [𝜂] (func (𝑥 𝑡𝑆 (Φ)) 𝑚(Ψ) (𝑥 𝜏) 𝜏 {return 𝑒}) ∈ 𝐷 𝜃 = (Φ,Ψ := 𝜙,𝜓 ) body(𝑡𝑆 (𝜙).𝑚(𝜓 )) = (𝑥 : 𝑡𝑆 (𝜙), 𝑥 : 𝜏).𝑒 [𝜃 ] (type 𝛼 𝜏𝐼 ) = Φ Φˆ = 𝛼 type(𝜏𝑆{𝑣}) = 𝜏𝑆 𝑚𝑀1,𝑚𝑀2 ∈ 𝑆 implies𝑀1 = 𝑀2 unique(𝑆) tdecls(𝐷) = [𝑡 | (type 𝑡 (Φ) 𝑇 ) ∈ 𝐷] mdecls(𝐷) = [𝑡𝑆 .𝑚 | (func (𝑥 𝑡𝑆 (Φ)) 𝑚𝑀 {return 𝑒}) ∈ 𝐷] (𝛼 : 𝜏𝐼 ) ∈ Δ boundsΔ (𝛼) = 𝜏𝐼 boundsΔ (𝜏𝑆 ) = 𝜏𝑆 boundsΔ (𝜏𝐼 ) = 𝜏𝐼 methodsΔ (𝑡𝑆 (𝜙)) = {(𝑚𝑀) [𝜂] | (func (𝑥 𝑡𝑆 (Φ)) 𝑚𝑀 {return 𝑒}) ∈ 𝐷, 𝜂 = (Φ :=Δ 𝜙)} type 𝑡𝐼 (Φ) interface {𝑆} ∈ 𝐷 𝜂 = (Φ := 𝜙) methodsΔ (𝑡𝐼 (𝜙)) = 𝑆 [𝜂] (𝛼 : 𝜏𝐼 ) ∈ Δ methodsΔ (𝛼) = methodsΔ (𝜏𝐼 ) Fig. 16. FGG auxiliary functions to before but now under type environment Δ. Judgement Φ ⊢ 𝑇 ok holds if under type formals Φ type literal 𝑇 is well formed, and again is a straightforward adjustment of its earlier definition. Judgement 𝐷 ok holds if declaration 𝐷 is well formed. The definitions are similar to previous definition. For a type declaration, its type formals must be well formed under the empty type environment, and its type literal must be well formed under the environment given by the type formals. For a method declaration, we require Φ; Ψ ok Δ, where Φ are the type formals of the receiver and Ψ are the type formals of the method. The receiver type must be declared with formals Φ′, where Φ <: Φ′. An alternative, simpler design would require Φ and Φ′ to be identical; but that would rule out the solution to the expression problem given in Section 2.3. The rest is a straightforward adjustment of its earlier definition. Judgement Δ; Γ ⊢ 𝑒 : 𝜏 holds if under type environment Δ and environment Γ expression 𝑒 has type 𝜏 . The adjustments are straightforward. Method calls are adjusted so that the type of the arguments and result are instantiated by the method type arguments. Type assertions are adjusted to take into account that type names are replaced by types. In method calls and type assertions, type parameters are treated as equivalent to the parameters’ bound. 4.4 FGG Reduction Figure 18 presents the FGG reduction rules. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:18 Griesemer et al. Implements Δ ⊢ 𝜏 <: 𝜎 Φ <: Ψ <:-param Δ ⊢ 𝛼 <: 𝛼 <:𝑆 Δ ⊢ 𝜏𝑆 <: 𝜏𝑆 <:𝐼 methodsΔ (𝜏) ⊇ methodsΔ (𝜏𝐼 ) Δ ⊢ 𝜏 <: 𝜏𝐼 <:-formals ∅ ⊢ 𝜏𝐼 <: 𝜎𝐼 (type 𝛼 𝜏𝐼 ) <: (type 𝛼 𝜎𝐼 ) Well-formed type and actuals Δ ⊢ 𝜏 ok Δ ⊢ 𝜙 ok t-param (𝛼 : 𝜏𝐼 ) ∈ Δ Δ ⊢ 𝛼 ok t-named Δ ⊢ 𝜙 ok (type 𝑡 (Φ) 𝑇 ) ∈ 𝐷 𝜂 = (Φ :=Δ 𝜙) Δ ⊢ 𝑡 (𝜙) ok t-actual 𝜏 = 𝜙 Δ ⊢ 𝜏 ok Δ ⊢ 𝜙 ok Well-formed type formals and nested formals Φ ⊢ Ψ ok Φ; Ψ ok Δ t-formal (type 𝛼 𝜏𝐼 ) = Ψ distinct (Φˆ, 𝛼) Φ,Ψ ⊢ 𝜏𝐼 ok Φ ⊢ Ψ ok t-nested ∅ ⊢ Φ ok Φ ⊢ Ψ ok Δ = Φ,Ψ Φ; Ψ ok Δ Well-formed method specifications and type literals Φ ⊢ 𝑆 ok Φ ⊢ 𝑇 ok t-specification Φ; Ψ ok Δ distinct (𝑥) Δ ⊢ 𝜏 ok Δ ⊢ 𝜏 ok Φ ⊢𝑚(Ψ) (𝑥 𝜏) 𝜏 ok t-struct distinct (𝑓 ) Φ ⊢ 𝜏 ok Φ ⊢ struct {𝑓 𝜏} ok t-interface unique(𝑆) Φ ⊢ 𝑆 ok Φ ⊢ interface {𝑆} Well-formed declarations 𝐷 ok t-type ∅ ⊢ Φ ok Φ ⊢ 𝑇 ok type 𝑡 (Φ) 𝑇 ok t-func distinct (𝑥, 𝑥) (type 𝑡𝑆 (Φ′) 𝑇 ) ∈ 𝐷 Φ <: Φ′ Φ; Ψ ok Δ Δ ⊢ 𝜏 ok Δ ⊢ 𝜎 ok Δ; 𝑥 : 𝑡𝑆 (Φˆ), 𝑥 : 𝜏 ⊢ 𝑒 : 𝜏 Δ ⊢ 𝜏 <: 𝜎 func (𝑥 𝑡𝑆 (Φ)) 𝑚(Ψ) (𝑥 𝜏) 𝜎 {return 𝑒} ok Expressions Δ; Γ ⊢ 𝑒 : 𝜏 < t-var (𝑥 : 𝜏) ∈ Γ Δ; Γ ⊢ 𝑥 : 𝜏 t-call (𝑚(Ψ) (𝑥 𝜎) 𝜎) ∈ methodsΔ (𝜏) Δ; Γ ⊢ 𝑒 : 𝜏 Δ; Γ ⊢ 𝑒 : 𝜏 𝜂 = (Ψ :=Δ 𝜓 ) Δ ⊢ (𝜏 <: 𝜎) [𝜂] Δ; Γ ⊢ 𝑒.𝑚(𝜓 ) (𝑒) : 𝜎 [𝜂] t-literal Δ ⊢ 𝜏𝑆 ok Δ; Γ ⊢ 𝑒 : 𝜏 (𝑓 𝜎) = fields(𝜏𝑆 ) Δ ⊢ 𝜏 <: 𝜎 Δ; Γ ⊢ 𝜏𝑆{𝑒} : 𝜏𝑆 t-field Δ; Γ ⊢ 𝑒 : 𝜏𝑆 (𝑓 𝜏) = fields(𝜏𝑆 ) Δ; Γ ⊢ 𝑒.𝑓𝑖 : 𝜏𝑖 t-assert𝐼 Δ ⊢ 𝜏𝐽 ok Δ; Γ ⊢ 𝑒 : 𝜎𝐽 Δ; Γ ⊢ 𝑒.(𝜏𝐽 ) : 𝜏𝐽 t-assert𝑆 Δ ⊢ 𝜏𝑆 ok Δ; Γ ⊢ 𝑒 : 𝜎𝐽 𝜏𝑆 <: boundsΔ (𝜎𝐽 ) Δ; Γ ⊢ 𝑒.(𝜏𝑆 ) : 𝜏𝑆 t-stupid Δ ⊢ 𝜏 ok Δ; Γ ⊢ 𝑒 : 𝜎𝑆 Δ; Γ ⊢ 𝑒.(𝜏) : 𝜏 Programs 𝑃 ok t-prog distinct (tdecls(𝐷)) distinct (mdecls(𝐷)) 𝐷 ok ∅; ∅ ⊢ 𝑒 : 𝜏 package main; 𝐷 func main() {_ = 𝑒} ok Fig. 17. FGG typing Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:19 Value 𝑣 ::= 𝜏𝑆{𝑣} Evaluation context 𝐸 ::= Hole □ Method call receiver 𝐸.𝑚(𝜏) (𝑒) Method call arguments 𝑣 .𝑚(𝜏) (𝑣, 𝐸, 𝑒) Structure 𝜏𝑆{𝑣, 𝐸, 𝑒} Select 𝐸.𝑓 Type assertion 𝐸.(𝜏) Reduction 𝑑 −→ 𝑒 r-field (𝑓 𝜏) = fields(𝜏𝑆 ) 𝜏𝑆{𝑣}.𝑓𝑖 −→ 𝑣𝑖 r-call (𝑥 : 𝜏𝑆 , 𝑥 : 𝜏).𝑒 = body(type(𝑣).𝑚(𝜓 )) 𝑣 .𝑚(𝜓 ) (𝑣) −→ 𝑒 [𝑥 := 𝑣, 𝑥 := 𝑣] r-assert ∅ ⊢ type(𝑣) <: 𝜏 𝑣 .(𝜏) −→ 𝑣 r-context 𝑑 −→ 𝑒 𝐸 [𝑑] −→ 𝐸 [𝑒] Fig. 18. FGG reduction The adjustment to values, the auxiliary function type, and to evaluation contexts are simple, replacing type names by types and adding type arguments as appropriate. Judgement 𝑑 −→ 𝑒 holds if expression 𝑑 steps to expression 𝑒 . Again, the adjustments are all simple. 4.5 FGG properties The results of the previous section adapt straightforwardly. Lemma 4.1 (Well formed). If Δ; Γ ⊢ 𝑒 : 𝜏 then Δ ⊢ 𝜏 ok. The substitution lemma is adapted to take into account that in a method declaration the type parameters of the receiver are substituted before the type parameters of the method. Lemma 4.2 (Substitution). Let 𝜂 = (𝛼 := 𝜏) be a substitution. • If ∅ ⊢ 𝛼 <: 𝜏𝐼 [𝜂] and 𝛼 : 𝜏𝐼 , Δ ⊢ 𝜏 ok then Δ[𝜂] ⊢ 𝜏 [𝜂] ok. • If ∅ ⊢ 𝛼 <: 𝜏𝐼 [𝜂] and 𝛼 : 𝜏𝐼 , Δ; Γ ⊢ 𝑒 : 𝜏 then Δ[𝜂]; Γ [𝜂] ⊢ 𝑒 [𝜂] : 𝜏 [𝜂]. • If ∅; ∅ ⊢ 𝑣 : 𝜏 and ∅; 𝑥 : 𝜎 ⊢ 𝑒 : 𝜎 and ∅ ⊢ 𝜏 <: 𝜎 then ∅; ∅ ⊢ 𝑒 [𝑥 := 𝑣] : 𝜏 for some type 𝜏 with ∅ ⊢ 𝜏 <: 𝜎 . The remaining results are easy to adjust. Theorem 4.3 (Preservation). If ∅; ∅ ⊢ 𝑑 : 𝜎 and 𝑑 −→ 𝑒 then ∅; ∅ ⊢ 𝑒 : 𝜏 for some 𝜏 with ∅ ⊢ 𝜏 <: 𝜎 . Expression 𝑑 panics if there exist evaluation context 𝐸, value 𝑣 , and type 𝜏 such that 𝑑 = 𝐸 [𝑣 .(𝜏)] and ∅ ⊢ type(𝑣) ≮: 𝜏 . Theorem 4.4 (Progress). If ∅; ∅ ⊢ 𝑑 : 𝜎 then either 𝑑 is a value, 𝑑 −→ 𝑒 for some 𝑒 , or 𝑑 panics. 5 MONOMORPHISATION The monomorphisation process consists of two phases. In the first phase, a set of types and method instantiations are collected from an FGG program. In the second phase, an FGG program is translated to its FG equivalent following the instance set computed in the first phase. Throughout this section, we illustrate the monomorphisation process with the FGG program in Figure 19 (left). This program contains a Dispatcher structure which processes abstract Events. A Dispatcher processes events, and events are objects that can be processed. For the sake of space, the program in Figure 19 includes only one implementation of Events, i.e., UIEvents, but other implementations may be easily added following the same pattern. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:20 Griesemer et al. type Any interface {} type Int struct {} type Event interface { Process(type b Any)(y b) Int } type UIEvent struct {} func (x UIEvent) Process(type b Any)(y b) Int { return Int{} } type Dispatcher struct {} func (x Dispatcher) Dispatch(y Event) Int { return y.Process(Int)(Int{}) } func main() { _ = Dispatcher{}.Dispatch(UIEvent{}) } type Top struct {} type Int struct {} type Event interface { Process(y Int) Int Process<1>() Top } type UIEvent struct {} func (x UIEvent) Process(y Int) Int { return Int{} } func (x UIEvent) Process<1>() Top { return Top{} } type Dispatcher struct {} func (x Dispatcher) Dispatch(y Event) Int { return y.Process(Int{}) } func (x Dispatcher) Dispatch<1>() Top { return Top{} } func main() { _ = Dispatcher{}.Dispatch(UIEvent{}) } Fig. 19. Dispatcher example: FGG source (left) and FG translation (right) 5.1 Collecting type and method instances Let 𝜔,Ω range over instance sets, which contain elements of type 𝜏 or pairs of a type with a method and its type arguments, 𝜏 .𝑚(𝜓 ). In Figure 20 we define a judgement 𝑃 ▶ Ω which computes the set of instances of types and methods required to correctly monomorphise an FGG program. Judgement Δ; Γ ⊢ 𝑒 ▶ 𝜔 holds if 𝜔 is the instance set for expression 𝑒 , given environments Δ and Γ. In the rules for variables, structure literals, field selections and type assertions, we simply collect the occurrences of type instances and proceed inductively. The rule for method calls additionally records the instantiation of the method 𝜏 .𝑚(𝜓 ) where 𝜏 is the type of its receiver. In the rules for structure literals and method calls, we assume that sequences of instance sets, e.g., 𝜔 , coerce to a set consisting of the union of the elements of the sequence. The instance set of a program 𝑃 is the limit of function 𝐺 applied to the instance set of its body. 𝐺Δ (𝜔) is defined via four auxiliary functions that compute the type and method instances required by 𝜔 . It returns all type and method instantiations that are required to monomorphise declarations (F-closure, M-closure) and to preserve the <: relation (I-closure, S-closure). F-closure finds all the type instances that occur in the declarations of structures, while M-closure finds all the type instances that occur in the declarations of method instances. I-closure finds all the method signature instances that are required to preserve the <: relation over interfaces. S-closure finds all the type and method sets required by method calls, inter-procedurally. For each method instance 𝜏 .𝑚(𝜓 ) in 𝜔 it finds all instance sets of all implementations of method 𝑚, following the <: relation. Intuitively, I-closure and S-closure are used to guarantee that if 𝜏 <: 𝜎 , then the monomorphised version of 𝜏 also implements the monomorphised version of 𝜎 . Consider the example in Figure 19 (left). For the top-level method, we have ∅; ∅ ⊢ Dispatcher{}.Dispatch(UIEvent{}) ▶ {Dispatcher, Dispatcher.Dispatch()} Posing 𝜔0 = {Dispatcher, Dispatcher.Dispatch()}, we compute the limit of 𝐺 applied to 𝜔0. We have 𝐺∅ (𝜔0) = 𝜔0 ∪ {Event, Int, Dispatcher.Dispatch(), Event, Event.Process(Int)} where Event and Int Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:21 Instance sets 𝜔,Ω 𝜔,Ω range over sets containing elements of the form 𝜏 or 𝜏 .𝑚(𝜓 ). Expressions and programs Δ; Γ ⊢ 𝑒 ▶ 𝜔 𝑃 ▶ Ω I-var Δ; Γ ⊢ 𝑥 ▶ ∅ I-literal Δ; Γ ⊢ 𝑒 ▶ 𝜔 Δ; Γ ⊢ 𝜏𝑆{𝑒} ▶ {𝜏𝑆 } ∪ 𝜔 I-field Δ; Γ ⊢ 𝑒 ▶ 𝜔 Δ; Γ ⊢ 𝑒.𝑓𝑖 ▶ 𝜔 I-assert Δ; Γ ⊢ 𝑒 ▶ 𝜔 Δ; Γ ⊢ 𝑒.(𝜏) ▶ {𝜏} ∪ 𝜔 I-call Δ; Γ ⊢ 𝑒 : 𝜏 Δ; Γ ⊢ 𝑒 ▶ 𝜔 Δ; Γ ⊢ 𝑒 ▶ 𝜔 Δ; Γ ⊢ 𝑒.𝑚(𝜓 ) (𝑒) ▶ {𝜏, 𝜏 .𝑚(𝜓 )} ∪ 𝜔 ∪ 𝜔 I-prog ∅; ∅ ⊢ 𝑒 ▶ 𝜔 Ω = lim 𝑛→∞𝐺 𝑛 ∅ (𝜔) package main; 𝐷 func main() {_ = 𝑒} ▶ Ω Auxiliary functions 𝐺Δ (𝜔) = 𝜔 ∪ F-closure(𝜔) ∪M-closureΔ (𝜔) ∪ I-closureΔ (𝜔) ∪ S-closureΔ (𝜔) F-closure(𝜔) = ⋃{ 𝜏 𝜏𝑆 ∈ 𝜔, (𝑓 𝜏) = fields(𝜏𝑆 )} M-closureΔ (𝜔) = ⋃{ 𝜎 [𝜂] ∪ {𝜎 [𝜂]} 𝜏 .𝑚(𝜓 ) ∈ 𝜔, (𝑚(Ψ) (𝑥 𝜎) 𝜎) ∈ methodsΔ (𝜏), 𝜂 = (Ψ :=𝜓 )} I-closureΔ (𝜔) = { 𝜏 ′𝐼 .𝑚(𝜓 ) 𝜏𝐼 .𝑚(𝜓 ) ∈ 𝜔, 𝜏 ′𝐼 ∈ 𝜔, Δ ⊢ 𝜏 ′𝐼 <: 𝜏𝐼 } S-closureΔ (𝜔) = ⋃{ {𝜏𝑆 .𝑚(𝜓 )} ∪ Ω 𝜏 .𝑚(𝜓 ) ∈ 𝜔, 𝜏𝑆 ∈ 𝜔, Δ ⊢ 𝜏𝑆 <: 𝜏, Δ; 𝑥 : 𝜏𝑆 , 𝑥 : 𝜎 ⊢ 𝑒 ▶ Ω(𝑥 : 𝜏𝑆 , 𝑥 : 𝜎).𝑒 = body(𝜏𝑆 .𝑚(𝜓 )) } Fig. 20. Computing instance sets are obtained from M-closure∅ (𝜔0), while Dispatcher.Dispatch(), Event, and Event.Process(Int) are obtained from S-closure∅ (𝜔0). The limit of function 𝐺 is reached after two iterations, i.e., lim𝑛→∞𝐺𝑛∅ (𝜔0) = 𝐺∅ (𝐺∅ (𝜔0)) = 𝐺∅ (𝜔0) ∪ {UIEvent.Process(Int)}. Note that UIEvent.Process(Int) is obtained via S-closure using the fact that UIEvent <: Event holds. 5.2 Monomorphisation judgement We now define a judgement ⊢ 𝑃 ↦→ 𝑃† where 𝑃 is a program in FGG, and 𝑃† is a corresponding monomorphised program in FG. This judgement is in turn defined by judgements for each of the syntactic categories in FGG. Some of the judgements are also parameterised by instance sets (ranged over by Ω), substitutions that map type parameters to ground types (ranged over by 𝜂), or method instance sets (ranged over by 𝜇). Figure 21 formalises how we recursively apply a consistent renaming to generate FG code. To monomorphise types, type formals, method names, and method formals, given a substitution 𝜂, we assume a map from closed types to identifiers. For instance, if f is a type with two arguments, and g and h are types with no arguments, then closed type f(g(), h()) might correspond to the identifier “f,h<>>” assuming “<,>” are allowed as letters in identifiers. We write 𝑡† = ⟨𝜏⟩ to compute the identifier 𝑡† that corresponds to closed type 𝜏 . Similarly, we write𝑚† = ⟨𝑚(𝜓 )⟩ to compute the identifier𝑚† that corresponds to closed method instantiation𝑚(𝜓 ). Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:22 Griesemer et al. Types and methods 𝜂 ⊢ 𝜏 ↦→ 𝑡† 𝜂 ⊢ 𝑡 (Φ) ↦→ 𝑡† 𝜂 ⊢𝑚(𝜓 ) ↦→𝑚† 𝜂 ⊢𝑚(Ψ) ↦→𝑚† m-type 𝑡† = ⟨𝜏 [𝜂]⟩ 𝜂 ⊢ 𝜏 ↦→ 𝑡† m-tformal 𝑡† = ⟨𝑡 (𝛼 [𝜂])⟩ 𝜂 ⊢ 𝑡 (type 𝛼 𝜏𝐼 ) ↦→ 𝑡† m-method 𝑚† = ⟨𝑚(𝜓 [𝜂])⟩ 𝜂 ⊢𝑚(𝜓 ) ↦→𝑚† m-mformal 𝑚† = ⟨𝑚(𝛼 [𝜂])⟩ 𝜂 ⊢𝑚(type 𝛼 𝜏𝐼 ) ↦→𝑚† Expression 𝜂 ⊢ 𝑒 ↦→ 𝑒† m-var 𝜂 ⊢ 𝑥 ↦→ 𝑥 m-value 𝜂 ⊢ 𝜏𝑆 ↦→ 𝑡†𝑆 𝜂 ⊢ 𝑒 ↦→ 𝑒† 𝜂 ⊢ 𝜏𝑆 {𝑒} ↦→ 𝑡†𝑆 {𝑒†} m-select 𝜂 ⊢ 𝑒 ↦→ 𝑒† 𝜂 ⊢ 𝑒.𝑓 ↦→ 𝑒† .𝑓 m-call 𝜂 ⊢ 𝑒 ↦→ 𝑒† 𝜂 ⊢𝑚(𝜓 ) ↦→𝑚† 𝜂 ⊢ 𝑒 ↦→ 𝑒† 𝜂 ⊢ 𝑒.𝑚(𝜓 ) (𝑒) ↦→ 𝑒† .𝑚† (𝑒†) m-assert 𝜂 ⊢ 𝑒 ↦→ 𝑒† 𝜂 ⊢ 𝜏 ↦→ 𝑡† 𝜂 ⊢ 𝑒.(𝜏) ↦→ 𝑒† .(𝑡†) Method signature 𝜂 ⊢ 𝑀 ↦→ 𝑀† 𝜂 ⊢ 𝑆 ↦→ 𝑆† m-sig 𝜂 ⊢ 𝜏 ↦→ 𝑡† 𝜂 ⊢ 𝜏 ↦→ 𝑢† 𝜂 ⊢ (𝑥 𝜏) 𝜏 ↦→ (𝑥 𝑡†) 𝑢† m-id hash(𝑚𝑀 [𝜂]) =𝑚∗ 𝜂 ⊢𝑚𝑀 ↦→𝑚∗ () Top Type literal 𝜂; 𝜇 ⊢ 𝑇 ↦→ 𝑇 † m-struct 𝜂 ⊢ 𝜏 ↦→ 𝑡† 𝜂; 𝜇 ⊢ struct{𝑓 𝜏} ↦→ struct{𝑓 𝑡†} m-interface 𝜂; 𝜇 ⊢ 𝑆 ↦→ S 𝜂; 𝜇 ⊢ interface{𝑆} ↦→ interface{ ⋃ S} Fig. 21. Monomorphisation of FGG into FG — name mapping To monomorphise an expression given a substitution 𝜂, we recursively monomorphise all the types and expressions contained within this expression. We proceed similarly to monomorphise method signatures in Rule m-sig. Rule m-id is used to generate a dummy method signature that represents uniquely the alpha- equivalence class of its FGG counterpart. The signature specifies no parameters and the return type Top. It is necessary to generate such methods to ensure that if a type does not implement another in an FGG program, then this is also the case in its monomorphised counterpart. We assume that hash(𝑚𝑀1) = hash(𝑚𝑀2) for all𝑀1 = 𝑀2, using the same notion of equality as in unique and <:. To monomorphise a structure given a substitution, we recursively monomorphise all the types contained within its field declarations. To monomorphise an interface, we recursively monomor- phise each of its signatures and flatten the result in a single sequence of declarations. Figure 22 formalises how declarations are generated from Ω. Here we let 𝑁 range over (𝑥 𝜏) 𝜏 . To monomorphise an interface specification we pass two environments. One is a substitution from type parameters to ground types 𝜂 and the other is a set of method instances 𝜇, i.e., a set of entries of type𝑚(𝜓 ). For each entry in 𝜇, we compute a new substitution 𝜃 which extends 𝜂 and is used to generate a monomorphised instance of the corresponding signature. In addition, we generate dummy signature 𝑆† that uniquely identifies the FGG signature. Each parameterised method may produce zero or more monomorphised instances, plus a dummy method signature. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:23 Interface specification 𝜂; 𝜇 ⊢ 𝑆 ↦→ S m-spec S = { 𝑚†𝑁 † 𝑚(𝜓 ) ∈ 𝜇, 𝜃 = (𝜂,Ψ :=𝜓 ), 𝜃 ⊢𝑚(Ψ) ↦→𝑚†, 𝜃 ⊢ 𝑁 ↦→ 𝑁 † } 𝜂 ⊢𝑚(Ψ)𝑁 ↦→ 𝑆† 𝜂; 𝜇 ⊢𝑚(Ψ)𝑁 ↦→ S ∪ {𝑆†} Declaration Ω ⊢ 𝐷 ↦→ D m-type D = { type 𝑡† 𝑇 † 𝑡 (𝜙) ∈ Ω, 𝜂 = (Φ := 𝜙), 𝜇 = {𝑚(𝜓 ) | 𝑡 (𝜙).𝑚(𝜓 ) ∈ Ω}, 𝜂; 𝜇 ⊢ 𝑇 ↦→ 𝑇 † } Ω ⊢ type 𝑡 (Φ) 𝑇 ↦→ D m-func D = { func (𝑥 𝑡†𝑆 ) 𝑚†𝑁 † {return 𝑒†} 𝑡𝑆 (𝜙).𝑚(𝜓 ) ∈ Ω, 𝜃 = (Φ := 𝜙,Ψ :=𝜓 ), 𝜃 ⊢ 𝑡𝑆 (Φ) ↦→ 𝑡†𝑆𝜃 ⊢𝑚(Ψ) ↦→𝑚†, 𝜃 ⊢ 𝑁 ↦→ 𝑁 †, 𝜃 ⊢ 𝑒 ↦→ 𝑒† } D ′ = { func (𝑥 𝑡†𝑆 ) 𝑆† {return Top{}} 𝑡𝑆 (𝜙) ∈ Ω, 𝜂 = (Φ := 𝜙), 𝜂 ⊢ 𝑡𝑆 (Φ) ↦→ 𝑡†𝑆 , 𝜂 ⊢𝑚(Ψ)𝑁 ↦→ 𝑆†} Ω ⊢ func (𝑥 𝑡𝑆 (Φ)) 𝑚(Ψ)𝑁 {return 𝑒} ↦→ D ∪ D ′ Program ⊢ 𝑃 ↦→ 𝑃† m-program package main; 𝐷 func main() {_ = 𝑒} ▶ Ω Ω ⊢ 𝐷 ↦→ D 𝐷† = {type Top struct {}} ∪ ⋃ D ∅ ⊢ 𝑒 ↦→ 𝑒† ⊢ package main; 𝐷 func main() {_ = 𝑒} ↦→ package main; 𝐷† func main() {_ = 𝑒†} Fig. 22. Monomorphisation of FGG into FG — instance generation, where 𝑁 ranges over (𝑥 𝜏) 𝜏 To monomorphise the declaration of a type 𝑡 given an instance set, for each instance of 𝑡 , we generate a substitution 𝜂 and a method instance set 𝜇. Then we recursively produce a monomor- phised declaration for each generated pair of 𝜂 and 𝜇. Note that each type declaration may produce zero or more monomorphised declarations. To monomorphise a method declaration given an instance set, we compute a substitution 𝜃 for each method instance in Ω. Then we produce a monomorphised version of a method for each of its instantiations. Note that each method declaration may produce zero or more monomorphised declarations. In addition, for each type instance and each of its methods, we generate a dummy method that returns an instance of Top. To monomorphise a program 𝑃 , we compute its instance set Ω then monomorphise its declaration and body given with respect to Ω. We additionally add the declaration of the empty structure Top. The FGG program in Figure 19 (left) is translated to the FG program on the righthand side of the figure. The translation starts with rule m-program where Ω = {Int, Event, Event.Process(Int), UIEvent, UIEvent.Process(Int), Dispatcher, Dispatcher.Dispatch()}. Rule m-type is used to generate the instances of types Int, Event, UIEvent, and Dispatcher. Rule m-func is used to generate the methods UIEvent.Process(Int) and Dispatcher.Dispatch(), as well as their dummy counterparts Process<1>() and Dispatch<1>(). Rule m-spec is used to generate the method signatures of interface Event, i.e., Event.Process(Int) and its dummy counterpart Process<1>(). Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:24 Griesemer et al. Not monomorphisable 𝑃 nomono 𝐷 nomono mn-program 𝐷𝑖 nomono package main; 𝐷 func main() {_ = 𝑒} nomono mn-func Δ = Φ,Ψ exists 𝑛 ∈ N and (𝑡𝑆 (𝜙).𝑚(𝜓 )) ∈ 𝐺𝑛Δ ({𝑡𝑆 (Φˆ), 𝑡𝑆 (Φˆ).𝑚(Ψˆ)}) s.t. Φ ≺ 𝜙 or Ψ ≺ 𝜓 func (𝑥 𝑡𝑆 (Φ)) 𝑚(Ψ) (𝑦 𝜏) 𝜏 {return 𝑒} nomono Occurs check Φ ≺ 𝜙 𝛼 ≺ 𝜏 𝜙 = 𝜏 𝛼𝑖 ≺ 𝜏𝑖 (type 𝛼 𝜏𝐼 ) ≺ 𝜙 𝜏 ≠ 𝛼 𝛼 ∈ fv(𝜏) 𝛼 ≺ 𝜏 Fig. 23. Monomorphisability check 5.3 Ruling out non-monomorphisable programs It is not possible to monomorphise all FGG programs since programs that contain polymorphic recursive methods may produce infinitely many type instances. To address this issue, we propose a predicate 𝑃 nomono which conservatively identifies programs that can produce infinitely many type instances. Programs for which this predicate does not hold are guaranteed to be monomorphisable. Note that there exist programs which produce finitely many type instances but for which the predicate does hold, e.g., programs containing a polymorphic recursive method that is never called. The predicate is formally defined in Figure 23, notably reusing our instance generation procedures. 𝑃 nomono holds if 𝐷 nomono holds for at least one of its method declarations. 𝐷 nomono holds if it is possible to find an element in its instance set, inductively constructed using function S-closure, such that the occurs check is satisfied. The occurs check holds when a type variable appears under a type constructor in a type instance or method call (in the same position it occupies in the type or method formal). We write fv(𝜏) for the set of type parameters occurring in 𝜏 . 5.4 Monomorphisation properties Not all programs are monomorphisable, however we can decide whether a program is monomor- phisable with the 𝑃 nomono predicate. Theorem 5.1 (Decidability). If 𝑃 ok then it is decidable whether or not 𝑃 nomono holds. For all well-typed programs 𝑃 such that 𝑃 nomono does not hold, their instance sets are finite. Theorem 5.2 (Monomorphisable). If 𝑃 ok and 𝑃 nomono doesn’t hold then 𝑃 ▶ Ω with Ω finite. Monomorphisation preserves typing, i.e., the translation of a well-typed FGG program is a well-typed FG program. Theorem 5.3 (Sound). If 𝑃 ok and ⊢ 𝑃 ↦→ 𝑃† then 𝑃† ok. The reduction behaviour of well-typed FGG programs is preserved and reflected by their monomorphised counterpart (see Figure 24). Write ⊢ 𝑑 ↦→ 𝑑† to abbreviate ∅; ∅ ⊢ 𝑑 ↦→ 𝑑†. Theorem 5.4 (Bisimulation). Let 𝑃 ok and ⊢ 𝑃 ↦→ 𝑃† with 𝑃 = 𝐷 ▷ 𝑑 and 𝑃† = 𝐷† ▷ 𝑑†. Then: (a) if 𝑑 −→ 𝑒 then there exists 𝑒† such that 𝑑† −→ 𝑒† and ⊢ 𝑒 ↦→ 𝑒†; (b) if 𝑑† −→ 𝑒† then there exists 𝑒 such that 𝑑 −→ 𝑒 and ⊢ 𝑒 ↦→ 𝑒†. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:25 𝑑 𝑑† 𝑒1 𝑒†1 ... ... 𝑒𝑛 𝑒†𝑛 ⊢ 𝑑 ↦→ 𝑑† ⊢ 𝑒1 ↦→ 𝑒†1 ⊢ 𝑒𝑛 ↦→ 𝑒†𝑛 Assume 𝑃 ok and ⊢ 𝑃 ↦→ 𝑃† with 𝑃 = 𝐷 ▷ 𝑑 and 𝑃† = 𝐷† ▷ 𝑑† Fig. 24. Bisimulation 6 IMPLEMENTATION We have made available a prototype implementation,1 which contains FG and FGG type checkers and interpreters, and a monomorphiser from FGG to FG (including the nomono check). We wrote the implementation in Go to facilitate interactions with the Go designers and community. Our interface includes some extensions to our tiny syntax for FG and FGG, such as direct support for interface embedding, some primitive data types, and minimal I/O. Versions of all the presented examples have been tested using the implementation. We took advantage of the implementation to perform extensive testing. FG evaluation results are compared to those using the official Go compiler, and the FG and FGG interpreters support dynamic checking of preservation and progress. To test monomorphisation, we added the test of bisimulation depicted in Figure 24: given a well-typed FGG program we generate its FG monomorphisation; we step the FGG and FG terms and confirm that the new FGG term monomorphises to the new FG term; and repeat until termination. Besides handcrafted examples and stress tests, we used NEAT [Claessen et al. 2015; Duregård 2016] to lazily enumerate all well-typed programs (up to some size relative to the total number of occurrences of method and type symbols) from a subset of FGG (similar to SmallCheck [Runciman et al. 2008]). The subset consists of programs which have: (1) at least one method and one field; (2) at most one empty interface; and (3) at most two empty structs. And where: (1) each method has at most two arguments; (2) each struct has at most two fields; (3) each interface has at most two members and two parents; and (4) each method or type constructor has at most two type parameters. Moreover, we disallow mutually recursive type definitions. These measures are taken to truncate the space of possible programs. We generate all FGG programs in our subset up to size 20, and confirm they pass the bisimulation test described above. 7 RELATEDWORK This paper is the first to present a core formalism for the Go language. Our presentation is styled after that of Featherweight Java by Igarashi et al. [2001]. Like them, we focus on a tiny, functional subset of the language; we define versions with and without generics; and we consider translation from one to the other. We also mark as “stupid” casts/type assertions that are disallowed in source but are required for reduction to preserve types. Our work resembles the development of generics for Java by Bracha et al. [1998] and for .NET by [Kennedy and Syme 2001] in that we build on a well-established base language. We note that in Featherweight Go, Featherweight Generic Go (and in the Go language), since method signatures are nonvariant, there are no fundamental decidability issues related to F-bounded polymorphism and variance [Pierce 1992], and so there is no need to consider more sophisticated techniques such as those of Greenman et al. [2014] to ensure decidability of type checking. In terms of formalisations of generics, prior work on generics adopts one, or a combination, of three main approaches, erasure, runtime representation of types as values, and monomorphisation. 1https://github.com/rhu1/fgg/ Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:26 Griesemer et al. Erasure. Bracha et al. [1998] present a translation from Java with generics to Java without generics that erases all information about type parameters. The translation relies on bridge methods, which in turn rely on method overloading, which is not supported in Go. Igarashi et al. [1999] formalised the translation for the FJ subset of Java (avoiding bridge methods) and proved it preserves typing and reductions. Downsides of erasure are that casts to generic types must be restricted and creation of generic arrays becomes tricky (see [Naftalin and Wadler 2006]). Moreover, erased code is often less efficient than monomorphised code. An upside is that erasure is linear in the size of the source, whereas monomorphisation can lead to an explosion in code size. Runtime representation. In contrast to Java erasure, Kennedy and Syme [2001] developed an extension of the .NET Common Language Runtime (CLR) and C# with direct support for generics. They generate a mixture of specialised and shared code: the former is compiled separately for each primitive type and is similar to monomorphisation; the latter is compiled once for every object type and is similar to erasure. JIT compilation is exploited to perform specialisation dynamically, avoiding potential code bloat. Code sharing is implemented by storing runtime type-reps [Crary et al. 1998] for type parameters. The overhead of runtime assembly of type-reps can be optimised by pre-computing and caching maps from open types to their reps when a generic type or method is instantiated [Kennedy and Syme 2001; Viroli and Natali 2000]. In future work, we will also look to techniques of optimising the coexistence of uniform (boxed) and non-uniform representations in polymorphic code [Leroy 1992] for dealing with the analogous mixture of struct and interface values in generic Go code. Monomorphisation. Although monomorphisation has been employed for languages such as C++, Standard ML and Rust [Turon 2015], we found a relative lack of peer-reviewed literature on this topic. This section discusses works related to monomorphisation that do not state or prove any correctness results. Stroustrup [2013, Chapter 26] describes template instantiation in C++. It is widely used, and infamous for code bloat. Benton et al. [1998] describes a whole-program compiler from SML’97 to Java bytecode, where polymorphism is fully monomorphised. Monomorphisation is alway possible, since Standard ML rules out polymorphic recursion (unlike FGG). Fluet [2015] sketches a similar approach used in the SML optimising compiler MLton. Tolmach and Oliva [1998, Section 6] develop a typed translation from an ML-like language to Ada, based on monomorphisation and presented in detail. Unlike us, they do not address subtyping (structural or otherwise) and they presume the absence of polymorphic recursion. Jones [1995] describes the use of specialisation to efficiently compile type classes for Haskell, which bears some resemblance to monomorphisation. Formalisation. We now consider works that formalise some aspect of monomorphisation. Yu et al. [2004] formalise the mixed specialisation and sharing mechanisms of the .NET JIT com- piler [Kennedy and Syme 2001]. The work describes a type and semantics preserving translation to a polymorphic .NET Intermediate Language (IL), where polymorphic behaviours are driven by type-reps [Crary et al. 1998], codifying runtime type data that can be used in e.g. dynamic casts. Their approach only generates code where type variables are instantiated with basic data types, using a uniform (i.e. boxed) representation for all other types. This sidesteps the key challenges of monomorphising code with polymorphic recursion and parameterised methods. Notably, Kennedy and Syme [2001] state that “some polymorphism simply cannot be specialized statically (polymor- phic recursion, first-class polymorphism)”. In contrast, we present an algorithm that can determine whether monomorphisation is possible in the presence of polymorphic recursion. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:27 Siek and Taha [2006] formalise the C++ template instantiation mechanism. They model partial specialization, template parameterisation and instantiation, and prove type soundness of template expansion. Unlike us, they do not state or prove bisimulation or preservation of reductions. Since C++ templates are Turing-complete, their soundness results are modulo termination, whereas our algorithms are guaranteed to terminate (see Theorem 5.1 and [Griesemer et al. 2020]). Tanaka et al. [2018, Section 2] report on a monomorphisation algorithm for Coq (Gallina) used in generation of low-level C code. Unlike us, they do not test for polymorphic recursion. Monomorphisation and logic. In a related area, Blanchette et al. [2016]; Bobot and Paskevich [2011] study monomorphisation for polymorphic first-order logic formulas, targeting the untyped or multi-sorted logics found in automated theorem provers. 8 CONCLUSION In this work we studied generics for a minimal subset of Go and their compilation via monomor- phisation. We chose monomorphisation since it is a simple first way of concretely explaining the semantics of generics using (simplified) Go and it avoids restrictions required by the erasure-based approach used in FGJ (e.g. type assertions on type variables). Another key benefit of monomorphi- sation is that of enabling 0-cost abstractions – programs that do not use generics incur no runtime penalty and generic code is translated into code that is equivalent to hand coded instantiations. The cost is that of requiring a whole program analysis and disallowing programs that would result in infinite instantiations (Section 5.3). Clearly, this is the beginning of the story, not the end. In future work, we plan to look at other methods of implementation beside monomorphisation, and in particular we plan to consider an implementation based on passing runtime representations of types, similar to that used for .NET generics [Kennedy and Syme 2001]. The idea is to automatically equip methods and structs with data that codifies type arguments used in generic code at runtime. For instance, a Cons struct would carry at runtime an additional field that specifies the type of the element contained in the cell, and a method that constructs a tree from a List would thread the runtime type information of the list cells into the tree. This approach requires translating all structs and methods of a program to account for runtime type passing and construction and thus the resulting programs can incur some runtime penalty. A mixed approach that uses monomorphisation sometimes and passing runtime representations sometimes might be best, again similar to that used for .NET generics. We will study the trade-offs and performance impact of this spectrum of approaches in future work. Featherweight Go is restricted to a tiny subset of Go. We plan a model of other important features such as assignments, arrays, slices, and packages, which we will dub Bantamweight Go; and a model of Go’s innovative concurrency mechanism based on “goroutines” and message passing, which we will dub Cruiserweight Go. ACKNOWLEDGMENTS We thank Nicholas Ng, Sam Lindley, and our referees for comments and suggestions. This work was funded under EPSRC EP/K034413/1, EP/T006544/1, EP/K011715/1, EP/L00058X/1, EP/N027833/1, EP/N028201/1, EP/T006544/1, EP/T014709/1 and EP/V000462/1, NCSS/EPSRC VeTSS, NOVA LINCS (UIDB/04516/2020) with the financial support of FCT- Fundação para a Ciência e a Tecnologia, and EU MSCA-RISE BehAPI (ID:778233). REFERENCES Nada Amin and Ross Tate. 2016. Java and Scala’s type systems are unsound: the existential crisis of null pointers. In Object-Oriented Programming: Systems, Languages, and Applications (OOPSLA). 838–848. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. 149:28 Griesemer et al. Nick Benton, Andrew Kennedy, and George Russell. 1998. Compiling Standard ML to Java Bytecodes. In Proceedings of the third ACM SIGPLAN International Conference on Functional Programming (ICFP ’98), Baltimore, Maryland, USA, September 27-29, 1998, Matthias Felleisen, Paul Hudak, and Christian Queinnec (Eds.). ACM, 129–140. https://doi.org/10.1145/ 289423.289435 Jasmin Christian Blanchette, Sascha Böhme, Andrei Popescu, and Nicholas Smallbone. 2016. Encoding Monomorphic and Polymorphic Types. Logical Methods in Computer Science 12, 4 (2016). François Bobot and Andrey Paskevich. 2011. Expressing Polymorphic Types in a Many-Sorted Language. In FroCoS (Lecture Notes in Computer Science, Vol. 6989). Springer, 87–102. Gilad Bracha, Martin Odersky, David Stoutamire, and Philip Wadler. 1998. Making the Future Safe for the Past: Adding Genericity to the Java Programming Language. In Proceedings of the 1998 ACM SIGPLAN Conference on Object-Oriented Programming Systems, Languages & Applications (OOPSLA ’98), Vancouver, British Columbia, Canada, October 18-22, 1998. ACM, 183–200. https://doi.org/10.1145/286936.286957 Peter Canning, William Cook, Walter Hill, Walter Olthoff, and John C Mitchell. 1989. F-bounded polymorphism for object-oriented programming. In Functional Programming Languages and Computer Architecture (FPCA). 273–280. Henry Cejtin, Suresh Jagannathan, and Stephen Weeks. 2000. Flow-directed closure conversion for typed languages. In European Symposium on Programming (ESOP). Springer, 56–71. Koen Claessen, Jonas Duregård, and Michał Pałka. 2015. Generating constrained random data with uniform distribution. Journal of Functional Programming 25 (2015). https://doi.org/10.1017/s0956796815000143 William R Cook. 1990. Object-oriented programming versus abstract data types. In Workshop of the REX Project (LNCS, Vol. 489). Springer, 151–178. Karl Crary, Stephanie Weirich, and J. Gregory Morrisett. 1998. Intensional Polymorphism in Type-Erasure Semantics. In Proceedings of the third ACM SIGPLAN International Conference on Functional Programming (ICFP ’98), Baltimore, Maryland, USA, September 27-29, 1998. ACM, 301–312. https://doi.org/10.1145/289423.289459 Sophia Drossopoulou and Susan Eisenbach. 1997. Java is type safe—probably. In European Conference on Object-Oriented Programming. Springer, 389–418. Jonas Duregård. 2016. Automating Black-Box Property Based Testing. Ph.D. Dissertation. Chalmers University of Technology. Matthew Flatt, Shriram Krishnamurthi, and Matthias Felleisen. 1998. Classes and Mixins. In Principles of Programming Languages (POPL). 171–183. Matthew Fluet. 2015. MLton – Monomorphise. http://mlton.org/Monomorphise. Ben Greenman, Fabian Muehlboeck, and Ross Tate. 2014. Getting F-bounded polymorphism into shape. In ACM SIGPLAN Conference on Programming Language Design and Implementation, PLDI ’14, Edinburgh, United Kingdom - June 09 - 11, 2014. 89–99. https://doi.org/10.1145/2594291.2594308 Robert Griesemer, Raymond Hu, Wen Kokke, Julien Lange, Ian Lance Taylor, Bernardo Toninho, Philip Wadler, and Nobuko Yoshida. 2020. Featherweight Go. CoRR abs/2005.11710 (2020). arXiv:2005.11710 https://arxiv.org/abs/2005.11710 Robert Griesemer, Rob Pike, Ken Thompson, Ian Taylor, Russ Cox, Jini Kim, and Adam Langley. 2009. Hey! Ho! Let’s Go! https://opensource.googleblog.com/2009/11/hey-ho-lets-go.html Atsushi Igarashi, Benjamin C. Pierce, and Philip Wadler. 1999. Featherweight Java: A Minimal Core Calculus for Java and GJ. In Proceedings of the 1999 ACM SIGPLAN Conference on Object-Oriented Programming Systems, Languages & Applications (OOPSLA ’99), Denver, Colorado, USA, November 1-5, 1999., Brent Hailpern, Linda M. Northrop, and A. Michael Berman (Eds.). ACM, 132–146. https://doi.org/10.1145/320384.320395 Atsushi Igarashi, Benjamin C. Pierce, and Philip Wadler. 2001. Featherweight Java: a minimal core calculus for Java and GJ. ACM Trans. Program. Lang. Syst. 23, 3 (2001), 396–450. https://doi.org/10.1145/503502.503505 Mark P Jones. 1995. Dictionary-free overloading by partial evaluation. Lisp and Symbolic Computation 8, 3 (1995), 229–248. Andrew Kennedy and Don Syme. 2001. Design and Implementation of Generics for the .NET Common Language Runtime. In Proceedings of the 2001 ACM SIGPLAN Conference on Programming Language Design and Implementation (PLDI), Snowbird, Utah, USA, June 20-22, 2001. ACM, 1–12. https://doi.org/10.1145/378795.378797 Shriram Krishnamurthi, Matthias Felleisen, and Daniel P Friedman. 1998. Synthesizing object-oriented and functional design to promote re-use. In European Conference on Object-Oriented Programming (ECOOP). Springer, 91–113. Xavier Leroy. 1992. Unboxed Objects and Polymorphic Typing. In Conference Record of the Nineteenth Annual ACM SIGPLAN- SIGACT Symposium on Principles of Programming Languages, Albuquerque, New Mexico, USA, January 19-22, 1992. ACM Press, 177–188. https://doi.org/10.1145/143165.143205 Maurice Naftalin and PhilipWadler. 2006. Java generics and collections. O’Reilly. http://www.oreilly.de/catalog/javagenerics/ index.html Tobias Nipkow and David von Oheimb. 1998. Javalight is Type-safe—Definitely. In Principles of Programming Languages (POPL). 161–170. Benjamin C. Pierce. 1992. Bounded Quantification is Undecidable. In Conference Record of the Nineteenth Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, Albuquerque, New Mexico, USA, January 19-22, Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020. Featherweight Go 149:29 1992. 305–315. https://doi.org/10.1145/143165.143228 John C Reynolds. 1994. User-defined types and procedural data structures as complementary approaches to data abstraction. In New Directions in Algorithmic Languages. MIT Press, 13–23. Colin Runciman, Matthew Naylor, and Fredrik Lindblad. 2008. Smallcheck and lazy smallcheck. In Proceedings of the first ACM SIGPLAN symposium on Haskell - Haskell '08. ACM Press. https://doi.org/10.1145/1411286.1411292 Jeremy G. Siek and Walid Taha. 2006. A Semantic Analysis of C++ Templates. In European Conference on Object-Oriented Programming (ECOOP) (LNCS, Vol. 4067). Springer, 304–327. Bjarne Stroustrup. 2013. The C++ Programming Language, 4th Edition. Addison-Wesley. Wouter Swierstra. 2008. Data types à la carte. Journal of Functional Programming 18, 4 (2008), 423–436. Don Syme. 1999. Proving Java Type Soundness. In Formal Syntax and Semantics of Java. Springer-Verlag, 83–118. Akira Tanaka, Reynald Affeldt, and Jacques Garrigue. 2018. Safe Low-level Code Generation in Coq UsingMonomorphization and Monadification. JIP 26 (2018), 54–72. Ian Lance Taylor and Robert Griesemer. 2019. Contracts — Draft Design. https://go.googlesource.com/proposal/+/master/ design/go2draft-contracts.md Ian Lance Taylor and Robert Griesemer. 2020. Type Parameters — Draft Design. https://go.googlesource.com/proposal/+/ refs/heads/master/design/go2draft-type-parameters.md The Go Team. 2020. The Go Programming Language Specification. https://golang.org/ref/spec The Rust Team. 2017. The Rust programming language. http://rust-lang.org/ Andrew P. Tolmach and Dino Oliva. 1998. FromML to Ada: Strongly-Typed Language Interoperability via Source Translation. Journal of Functional Programming 8, 4 (1998), 367–412. Mads Torgersen. 2004. The expression problem revisited. In European Conference on Object-Oriented Programming (ECOOP). Springer, 123–146. Aaron Turon. 2015. Abstraction without overhead: traits in Rust. https://blog.rust-lang.org/2015/05/11/traits.html Mirko Viroli and Antonio Natali. 2000. Parametric polymorphism in Java: an approach to translation based on reflective features. In Proceedings of the 2000 ACM SIGPLAN Conference on Object-Oriented Programming Systems, Languages & Applications (OOPSLA 2000), Minneapolis, Minnesota, USA, October 15-19, 2000. ACM, 146–165. https://doi.org/10.1145/ 353171.353182 Philip Wadler. 1998. The expression problem. Posted on the Java Genericity mailing list. http://homepages.inf.ed.ac.uk/ wadler/papers/expression/expression.txt Dachuan Yu, Andrew Kennedy, and Don Syme. 2004. Formalization of generics for the .NET common language runtime. In Proceedings of the 31st ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages, POPL 2004, Venice, Italy, January 14-16, 2004. ACM, 39–51. https://doi.org/10.1145/964001.964005 Matthias Zenger and Martin Odersky. 2004. Independently extensible solutions to the expression problem. Technical Report. Proc. ACM Program. Lang., Vol. 4, No. OOPSLA, Article 149. Publication date: November 2020.