Solving the Funarg Problem with Static Types IFL ’21, September 01–03, 2021, Online
Total Page:16
File Type:pdf, Size:1020Kb
Solving the Funarg Problem with Static Types Caleb Helbling Fırat Aksoy [email protected] fi[email protected] Purdue University Istanbul University West Lafayette, Indiana, USA Istanbul, Turkey ABSTRACT downwards. The upwards funarg problem refers to the manage- The difficulty associated with storing closures in a stack-based en- ment of the stack when a function returns another function (that vironment is known as the funarg problem. The funarg problem may potentially have a non-empty closure), whereas the down- was first identified with the development of Lisp in the 1970s and wards funarg problem refers to the management of the stack when hasn’t received much attention since then. The modern solution a function is passed to another function (that may also have a non- taken by most languages is to allocate closures on the heap, or to empty closure). apply static analysis to determine when closures can be stack allo- Solving the upwards funarg problem is considered to be much cated. This is not a problem for most computing systems as there is more difficult than the downwards funarg problem. The downwards an abundance of memory. However, embedded systems often have funarg problem is easily solved by copying the closure’s lexical limited memory resources where heap allocation may cause mem- scope (captured variables) to the top of the program’s stack. For ory fragmentation. We present a simple extension to the prenex the upwards funarg problem, an issue arises with deallocation. A fragment of System F that allows closures to be stack-allocated. returning function may capture local variables, making the size of We demonstrate a concrete implementation of this system in the the closure dynamic. Due to this behavior, the closure itself can- Juniper functional reactive programming language, which is de- not be copied down the stack without causing stack corruption. signed to run on extremely resource limited Arduino devices. We We must keep in mind that, as soon as the function returns, any also discuss other solutions present in other programming languages local variables will be lost when they are popped off the stack. that solve the funarg problem but haven’t been formally discussed In 1968, Weizenbaum [12] made an analysis of function closures in the literature. using lambda calculus, and showed that function closures in lambda calculus cannot have a stack based data structure and must in fact CCS CONCEPTS consist of a tree. Moses [9] further analyzed the problem for the Algol programming language. Early attempts to solve the funarg • Software and its engineering → Procedures, functions and problem [10] appear to be hampered by a number of factors, includ- subroutines; Functional languages. ing dynamic scoping, lack of static types and mutable variables. KEYWORDS Despite the titles of these early papers, we contend that the fu- narg problem (in particular, the upwards funarg problem) has not funarg, closure, stack, heap, memory, functional, embedded been adequately solved. In our approach, we solve the upwards fu- ACM Reference Format: narg problem by statically determining a closure’s size at compile Caleb Helbling and Fırat Aksoy. 2021. Solving the Funarg Problem with time. This allows the closure from the upwards funarg problem to Static Types. In IFL ’21: ACM Symposium on Implementation and Application be copied into the calling function, as if it were any other stati- of Functional Languages, September 01–03, 2021, Online. ACM, New York, cally sized piece of data. Since the type of a closure’s lexical scope NY, USA, 5 pages. https://doi.org/10.1145/1122445.1122456 can be viewed as a structural record type, we can make our lan- guage polymorphic over closures and their lexical scopes. This al- 1 INTRODUCTION lows higher order functions such as map (downwards funarg prob- lem) and compose (upwards and downwards funarg problem) to arXiv:2108.07389v2 [cs.PL] 22 Aug 2021 The funarg problem refers to the difficulty associated with compi- lation of first-class functions in programming languages for stack- be written very concisely and naturally. based environments. The problem arises in the bodies of nested Analyzing closures from a type theoretic perspective is not a functions where the nested function refers to identifiers defined new exercise [1, 6–8]. These papers prefer to treat closures as ex- in the parent function’s lexical scope which are not available in plicit environments, whose type is determined by an existential. it’s own lexical scope. The standard solution is to allocate closures This is not helpful to solving the funarg problem, since existen- on the heap. There are two variants of this problem: upwards and tials are typically treated as boxed values and must be allocated on the heap. Therefore our contribution is unique in the sense that Permission to make digital or hard copies of all or part of this work for personal or we seek to solve the funarg problem rather than analyzing closures classroom use is granted without fee provided that copies are not made or distributed from a purely type theoretic environmental perspective. for profit or commercial advantage and that copies bear this notice and the full cita- tion on the first page. Copyrights for components of this work owned by others than ACM must be honored. Abstracting with credit is permitted. To copy otherwise, or re- publish, to post on servers or to redistribute to lists, requires prior specific permission 1.1 Contributions and/or a fee. Request permissions from [email protected]. We describe the following contributions: IFL ’21, September 01–03, 2021, Online © 2021 Association for Computing Machinery. ACM ISBN 978-1-4503-XXXX-X/18/06...$15.00 • We demonstrate how the prenex fragment of System F can https://doi.org/10.1145/1122445.1122456 be extended to enable stack allocated closures. IFL ’21, September 01–03, 2021, Online Helbling and Aksoy environment along with the lambda’s argument for further evalu- Expressions ation. Figure 1 presents the big-step semantics for our language. 4 := G | _G : .4 | 41 42 | ΛU.4 | 4 = Δ( ) Type Schemes E G Δ [EVAL-VAR] f := |∀0.f ⊢ G ⇓ E Types , , := U | − → | X Δ ⊢ _G : .4 ⇓ (_G.4)[{~ 7→ Δ(~) | ~ ∈ fv(_G : .4)}] [EVAL-ABS] Lexical Scope Types X := {G : , ..., G= : = } 0 0 ′ ′ Δ1 ⊢ 41 ⇓ (_G.43)[Δ2] Δ1 ⊢ 42 ⇓ E Δ2, G 7→ E ⊢ 43 ⇓ E Values Δ1 ⊢ 41 42 ⇓ E E := (_G.4)[Δ] | Λ.4 [EVAL-APP] Contexts Γ ::= ∅ | Γ,U | Γ, G : [EVAL-TABS] Δ ⊢ ΛU.4 ⇓ Λ.4 Environments Δ := {G 7→ E , ..., G= 7→ E= } Δ ⊢ 41 ⇓ Λ.42 Δ ⊢ 42 ⇓ E 0 0 [EVAL-TAPP] Δ ⊢ 41 ⇓ E Table 1: Grammar Figure 1: Big step semantics • We discuss a concrete implementation of this approach in 2.2 Typing the Juniper programming language. Figure 2 presents the typing rules for our language. There are sev- • We analyze the limitations of the extension and identify the eral changes made to the type rules for the prenex fragment of Sys- connection between types and closures. tem F. Of primary interest is the T-ABS rule which also constructs • We make an analysis of other approaches found in exist- the type of the lexical scope. The construction of the lexical scope ing programming languages, but haven’t been discussed for- itself is performed by T-DELTA, which finds all free variables within mally in the literature. the lambda, looks up their type in Γ and returns the corresponding lexical scope type. The rule T-TABS is also changed to restrict type 2 EXTENDING SYSTEM F abstractions to contain no free variables themselves which simpli- In this section, we restrict and extend System F to enable stack allo- fies the type system and assists in compilation to a stack based cated closures. We restrict System F to it’s prenex fragment which environment. separates regular System F types into type schemes and types. This is a necessary restriction to prevent the emergence of existential 2.3 Examples types in the type system which prevents determining the sizes of In this section, we will look at two examples that demonstrate that types at compile-time. The primary addition is that of the closure our solution for the funarg problem works for accurately typing types, which are defined as function types with their lexical scopes two of the most commonly used functions in functional program- attached to them. The lexical scopes X are either type variables ming: map and compose. For the map example, we will assume or records of captured variables and their respective types. These that the type system is enriched with a list type constructor. The lexical scope types are are considered equivalent if both the field type of these functions are considerably more noisy than the stan- names and the types of those fields are identical. These additions dard System F types. Fortunately, most of the noise can be elided make it possible for closures to be polymorphic and enable stack through type inference. allocation of the closure since the size of the closure is known stati- cally at compile-time. Table 1 defines the grammar of our language. <0? : ∀U.∀V.∀X.(U−X → V)−{} → U list−{5 : U−X → V}→ V list 2.1 Big-step semantics For the compose example we give the definition of compose as Stack allocation of closures require the lambda values and their lex- well as its type in our System F extension. ical scopes to be propagated and used in pairs. For this reason, we combine the lambda values with their environments into a single 2><?>B4 := ΛU.ΛV.ΛW.ΛX1 .ΛX2. value representation.