First-Class Modules for Haskell Mark Shields Simon Peyton Jones Microsoft Research Cambridge {Markshie, Simonpj}@Microsoft.Com

First-Class Modules for Haskell Mark Shields Simon Peyton Jones Microsoft Research Cambridge {Markshie, Simonpj}@Microsoft.Com

Submitted to the Ninth International Conference on Foundations of Object-Oriented Languages First-Class Modules for Haskell Mark Shields Simon Peyton Jones Microsoft Research Cambridge fmarkshie; [email protected] Abstract gether towards this goal. ² Record types, with ¯elds of polymorphic type, dot no- Though Haskell's module language is quite weak, its core tation, and the ability to use a single ¯eld name in language is highly expressive. Indeed, it is tantalisingly close distinct record types (Section 2.1). to being able to express much of the structure traditionally ² Nested type declarations inside such records (Sec- delegated to a seperate module language. However, the en- tion 2.3). These nested declarations are purely syn- codings are awkward, and some situations can't be encoded tactic sugar; there is nothing complicated. at all. ² First-class universal and existential quanti¯cation (Sec- In this paper we re¯ne Haskell's core language to support tion 4.1). Together with record types, this allows us ¯rst-class modules with many of the features of ML-style conveniently to express the types of (generative) func- modules. Our proposal cleanly encodes signatures, struc- tors. tures and functors with the appropriate type abstraction and ² A declaration-oriented construct for opening an type sharing, and supports recursive modules. All of these existentially-quanti¯ed value (Section 4.2), together features work across compilation units, and interact harmo- with a notation to allow opened types to appear in niously with Haskell's class system. Coupled with support type annotations (Section 4.3). The standard approach for staged computation, we believe our proposal would be an is expression-oriented, which is unbearably clumsy in elegant approach to run-time dynamic linking of structured practice, whereas our construct works ¯ne at the top code. level (Section 4.4). Our work builds directly upon Jones' work on parameterised Taken individually, all of these ideas have been proposed signatures, Odersky and LÄaufer's system of higher-ranked before. Our contribution is to put them all together in type annotations, Russo's semantics of ML modules using a coherent design for a core language that can reasonably ordinary existential and universal quanti¯cation, and Oder- claim to compete with, and in some ways improve on, the sky and Zenger's work on nested types. We motivate the ML brand leader. In particular, our system treats module system by examples, and include a more formal presenta- structures as ¯rst-class values, supports type inference, and tion in the appendix. interacts harmoniously with Haskell's constrained polymor- phism. From the module point of view, it separates signa- tures from structures, and o®ers type abstraction, generative 1 Introduction functors, type sharing, separate compilation, and recursive and nested signatures and structures. Our proposal is, at the There are two competing techniques for expressing the large- top-level, fairly compatible with Haskell's existing module scale structure of programs. The \brand leader" is the two- system (though for clarity we shall bend the syntax some- level approach, in which the language has two layers: a core what in this paper). language, and a module language. The most sophisticated We present our system by a series of worked examples. A example of this structure is ML and its variants, but many more formal presentation may be found in the appendix. other languages, such as Haskell or Modula, take the same At the time of writing we have only just begun to estab- form, only with weaker module languages. lish the formal properties of our system. We have, however, In the last few years, however, the core language of (extended implemented a prototype interpreter which is available for versions of) Haskell has become very rich, to the point where download, and hope to merge these extensions into GHC, a it is tantalisingly close to being able to compete in the large- production Haskell compiler. scale-structure league. If that were possible, it would of course be highly desirable: it would remove the need for a second language; and it would automatically mean that 2 Concrete Modules as Records modules were ¯rst-class citizens, so that functors become ordinary functions. Following Jones [6], we encode interfaces as parameterised The purpose of this paper is to show that, by bringing to- record types, and implementations as records. Haskell al- gether several separate pieces of existing work, we can indeed ready has some record-like syntax for data constructors with bridge this ¯nal gap. More speci¯cally, we propose several named arguments, and many Haskell implementations allow more-or-less orthogonal extensions to Haskell that work to- these ¯elds to be assigned a polymorphic type. However, our 1 requirements are more demanding, as we wish to share ¯eld mkListSet' :: forall a . Eq a => Set a [] names between records, and allow nested type declarations. mkListSet' = Set { So we begin by introducing a new form of type declaration. empty = [] add = \x xs -> x : filter ((/=) x) xs asList = id 2.1 Parameterised Records } Record types are introduced (only) by explicit declaration, and may be parameterised: By using the overloaded operator (/=) we have replaced the explicit parameterisation over the record EqR a with implicit record Set a f = { parameterisation over the class Eq a. empty :: f a add :: a -> f a -> f a Record ¯elds may have polymorphic types: union :: f a -> f a -> f a record Monad f = { asList :: f a -> [a] fmap :: forall a b . (a -> b) -> f a -> f b } unit :: forall a . a -> f a bind :: forall a b . f a -> (a -> f b) -> f b (Note that f has kind Type -> Type.) Equality between } record types is nominal rather than structural. Unlike Haskell, a single ¯eld name may be re-used in di®erent record Such records may be constructed and taken apart in the types. same way as before: Record terms are constructed by applying a record construc- tor to a set of (possibly mutually recursive1) bindings: listMonad :: Monad [] {- inferred -} listMonad = Monad { intListSet :: Set Int [] {- inferred -} fmap = map intListSet = Set { unit = \a -> [a] empty = [] bind = \ma f -> concat (map f ma) add = \(x :: Int) xs -> x : filter (/= x) xs } union = foldr add asList = id singleton :: a -> [a] } singleton x = listMonad.unit x Record terms may be used within patterns, but we also sup- We do not permit subtyping or extensibility for records, de- port the usual \dot notation" for ¯eld projection: ferring such extensions to future work. one :: [Int] {- inferred -} one = intListSet.asList 2.2 Type inference (intListSet.add 1 intListSet.empty) Type inference in this system is problematic. For example, As in Haskell, the type signature on a binding | such as consider: one :: [Int] | is optional; the system will infer a type for g = \m f x -> m.fmap f (m.unit x) one, but the programmer may constrain the type with a type signature. Since fmap may be a ¯eld name in many records, the type of Regarding a module as a record allows an ML functor to be m.fmap depends on the type of m | which we do not know. replaced by an ordinary function. For example: We avoid this, and other di±culties relating to higher-ranked record EqR a = { eq :: a -> a -> Bool } polymorphism, by placing imposing the binder rule: the pro- grammer must supply a type annotation for every lambda- mkListSet :: forall a . EqR a -> Set a [] bound, or letrec-bound, variable whose type mentions a record mkListSet eq = Set { type constructor. With the binder rule in place, it becomes empty = [] easy to share ¯eld names between distinct record types. The add = \x xs -> binder rule is somewhat conservative | a clever inference x : filter (\y -> not (eq.eq x y)) xs engine could sometimes do without such an annotation | asList = id but it ensures that the typability of a program does not } depend on the inference algorithm. We discuss alternative approaches in Section 6. Since \functors" are ordinary functions, they integrate In practice, it may be tricky to give such a type annotation. smoothly with Haskell's type class mechanism: In our example, the type of m is Monad ®, where ® is the type in which g is polymorphic. We provide two ways to 1This is another (cosmetic, but important) di®erence solve this, both of which have been validated by practical from Haskell 98 records. experience in GHC. First, we can suppply a type signature 2 for g rather than m: transClosure :: Rep -> Rep } g :: forall m a b . Monad m -> (a -> b) -> a -> m b g = \m f x -> m.fmap f (m.unit x) We may reference nested data and record constructors within terms by a similar projection syntax: Here, we rely on the type checker to propagate the type leaf :: forall a . Set a^BinTree {- inferred -} annotation for g to an annotation for m, in the \obvious" leaf = Set^Leaf way | this statement can be made precise, but we do not edge :: Graph Int^Edge {- inferred -} do so here. Alternatively, g's argument m may be annotated edge = Graph^Edge { from = 1; to = 2 } directly: g = free t in \(m :: Monad t) f x -> Notice that type inference supplies the necessary type argu- m.fmap f (m.unit x) ments for Set and Graph. Record terms whose types contain nested types are con- Here the term free t in ... introduces a fresh type variable structed in the usual way: t standing for any type within the scope of a term. Thus g's trivGraph :: Graph () {- inferred -} ¯rst argument may be assigned any type of the form Monad ¿ trivGraph = Graph { for some type ¿. mkGraph = \vs es -> Rep [()] [Edge { from = (); to = () }] 2.3 Nested Type Declarations transClosure = \r -> r } Modules typically contain a mix of term-level and type-level declarations.

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    20 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us