Functional Pearl: Implicit Configurations
Total Page:16
File Type:pdf, Size:1020Kb
Functional Pearl: Implicit Configurations —or, Type Classes Reflect the Values of Types Oleg Kiselyov Chung-chieh Shan Fleet Numerical Meteorology Division of Engineering and Applied and Oceanography Center Sciences, Harvard University Monterey, CA 93943, USA Cambridge, MA 02138, USA [email protected] [email protected] ABSTRACT 1. INTRODUCTION The configurations problem is to propagate run-time preferences Most programs depend on configuration parameters. For exam- throughout a program, allowing multiple concurrent configuration ple, a pretty-printing function needs to know the width of the page, sets to coexist safely under statically guaranteed separation. This modular arithmetic depends on the modulus, numerical code heav- problem is common in all software systems, but particularly acute ily depends on tolerances and rounding modes, and most end-user in Haskell, where currently the most popular solution relies on un- applications depend on user preferences. Sometimes the param- safe operations and compiler pragmas. eters of the computation are known when the code is written or We solve the configurations problem in Haskell using only sta- compiled. Most of the time, however, the parameters are initialized ble and widely implemented language features like the type-class at the beginning of the computation, such as read from a configu- system. In our approach, a term expression can refer to run-time ration file. Sometimes the parameters stay the same throughout a configuration parameters as if they were compile-time constants program execution, but other times they need to be re-initialized. in global scope. Besides supporting such intuitive term notation For example, numerical code may need to be re-executed with dif- and statically guaranteeing separation, our solution also helps im- ferent rounding modes [12]. Also, a cryptography program may prove the program’s performance by transparently dispatching to need to perform modular arithmetic with various moduli. Library specialized code at run-time. We can propagate any type of config- code especially should support multiple sets of parameters that are uration data—numbers, strings, IO actions, polymorphic functions, simultaneously in use, possibly in different threads. closures, and abstract data types. No previous approach to propa- We refer to the problem of setting and propagating preference gating configurations implicitly in any language provides the same parameters as the configurations problem. We use the term “con- static separation guarantees. figurations” in the plural to emphasize that we aim to parameterize The enabling technique behind our solution is to propagate val- code at run time for several concurrent sets of preferences. ues via types, with the help of polymorphic recursion and higher- A solution to the configurations problem should keep configura- rank polymorphism. The technique essentially emulates local type- tion parameters out of the way: code that uses no parameters should class instance declarations while preserving coherence. Configu- not require any change. In particular, the programmer should not be ration parameters are propagated throughout the code implicitly as forced to sequence the computation (using a monad, say) when it part of type inference rather than explicitly by the programmer. Our is not otherwise needed. The parameters should be statically typed technique can be regarded as a portable, coherent, and intuitive al- and fast to access—ideally, just like regular lexical variables. Con- ternative to implicit parameters. It motivates adding local instances figuration data should be allowed to become known only at run to Haskell, with a restriction that salvages principal types. time. Moreover, different configurations should be able to coexist. When different configurations do coexist, the user should be stat- Categories and Subject Descriptors: D.1.1 [Programming ically prevented from inadvertently mixing them up; such subtle Techniques]: Applicative (Functional) Programming; D.3.2 errors are easy to make when the first goal above (that configura- [Language Classifications]: Haskell; D.3.3 [Programming Tech- tion parameters be implicit) is achieved. The solution should be niques]: Language Constructs and Features—abstract data types; available on existing programming language systems. polymorphism; recursion Given how pervasive the configurations problem is, it is not sur- General Terms: Design, Languages prising that the topic provokes repeated discussions in mailing lists [1, 7, 29], conferences [19], and journals [9]. As these discussions Keywords: Type classes; implicit parameters; polymorphic recur- conclude, no previous solution is entirely satisfactory. sion; higher-rank polymorphism; existential types Historically, the configurations problem is “solved” with muta- ble global variables or dynamically-scoped variables. Neither so- lution is satisfactory, because concurrent sets of parameters are hard to support reliably, be the language pure or impure, func- tional or imperative. Furthermore, in a pure functional language like Haskell, mutable global variables are either unwieldy (all code is written in monadic style) or unsafe (unsafePerformIO is used). Another common solution is to store configuration data in a glob- ally accessible registry. That approach suffers from run-time over- An appendectomized version of this Harvard University technical report is head and often the loss of static typing. Finally, one type-safe and published in the proceedings of the 2004 Haskell Workshop [15]. pure approach is to place all configuration data into a record and mance by specializing code at run-time. Along the way, we survey pass it from one function to another. However, it is unappealing existing attempts at solving the configurations problem. For multi- to do so explicitly throughout the whole program, not the least be- ple configurations, our solution is more portable, coherent, and in- cause managing concurrent sets of parameters is error-prone. tuitive than implicit parameters. Finally, our technique effectively Implicit parameters [19] are a proposal to extend Haskell with declares local type-class instances, which prompts us to sketch an dynamically-scoped variables like LISP’s [28]. As a solution to extension to Haskell. the configurations problem, implicit parameters inherit from dy- This paper is organized as follows. In Section 2, we discuss the namically-scoped variables the difficulty of supporting concurrent configurations problem and survey previous attempts at solving it. sets of parameters: they interact unintuitively with other parts of We demonstrate why these attempts are unsatisfactory and illustrate Haskell [26] and easily lead to quite subtle errors [9], whether or how acute the problem is if otherwise pure functional programmers not the monomorphism restriction is abolished. As Peyton Jones are willing to resort to operations with no safety guarantee. Sec- [26] puts it, “it’s really not clear what is the right thing to do.” tion 3 introduces the running example of modular arithmetic. This In this paper, we present a solution to the configurations prob- example calls for the peaceful coexistence and static separation of lem in Haskell that meets all the requirements enumerated above. several concurrent configurations. Section 4 develops our solution We rely on type classes, higher-rank polymorphism, and—in ad- in three stages: passing integers; passing serializable data types, vanced cases—the foreign function interface. These are all well- including floating-point numbers and strings; and finally, passing documented and widely-implemented Haskell extensions, for in- any type, including functional and abstract values. In Section 5 stance in Hugs and in the Glasgow Haskell Compiler. The notation we present two real-world examples to demonstrate that our solu- is truly intuitive; for example, the term tion scales to multiple parameters and helps the programmer write foo :: (Integral a, Modular s a) ⇒ M s a fast code with intuitive notation. Our solution improves over the foo = 1000 × 1000 × 5 + 2000 OpenSSL cryptography library, where the lack of static separation guarantees seems to have stunted development. In Section 6, we expresses a modular computation in which each addition and multi- compare our solution to previous work, especially Lewis et al.’s 1 plication is performed modulo a modulus. The type signature here implicit parameters [19]. We argue for adding local type-class in- describes a polymorphic “modulus-bearing number” of type M s a. stances to Haskell and sketch how. We then conclude in Section 7. As we detail in Section 3, the type-class constraints require that the type a be an Integral type (such as Int), and that the type s carry 2. THE CONFIGURATIONS PROBLEM configuration data for Modular arithmetic on a. The modulus is supplied at run time, for example: A Haskell program is a collection of definitions, which are rarely closed. For example, withIntegralModulus0 1280 foo result approx = last $ take maxIter $ iterate approx (pi / 2) The same computation can be re-executed with different moduli: 0 is an open definition: last, take, iterate, pi, and maxIter are defined [withIntegralModulus m foo | m ← [1 .. 100]] elsewhere. The values associated with these symbols are known at We take advantage of the compiler’s existing, automatic type infer- compile time. Such a static association is proper for pi, which is ence to propagate configuration data as type-class constraints. Thus truly a constant. However, maxIter is more of a user