First-Class Dynamic Types (2019)
Total Page:16
File Type:pdf, Size:1020Kb
First-Class Dynamic Types∗ Michael Homer Timothy Jones James Noble School of Engineering Montoux School of Engineering and Computer Science New York, USA and Computer Science Victoria University of Wellington [email protected] Victoria University of Wellington New Zealand New Zealand [email protected] [email protected] Abstract 1 Introduction Since LISP, dynamic languages have supported dynamically- Most dynamically-typed languages do not provide much sup- checked type annotations. Even in dynamic languages, these port for programmers incorporating type checks in their annotations are typically static: tests are restricted to check- programs. Object-oriented languages have dynamic dispatch ing low-level features of objects and values, such as primi- and often explicit “instance of” reflective operations, grad- tive types or membership of an explicit programmer-defined ual typing provides mixed-mode enforcement of a single class. static type system, and functional languages often include We propose much more dynamic types for dynamic lan- pattern-matching partial functions, all of which are relatively guages — first-class objects that programmers can custom- limited at one end of the spectrum. At the other end, lan- ise, that can be composed with other types and depend on guages like Racket support higher-order dynamic contract computed values — and to use these first-class type-like val- systems, wrapping objects to verify contracts lazily, and with ues as types. In this way programs can define their own con- concomitant power, complexity, and overheads [10, 51]. ceptual models of types, extending both the kinds of tests Programmers may wish for a greater level of run-time programs can make via types, and the guarantees those tests checking than provided by the language, or merely different can provide. Building on a comprehensive pattern-matching checking. In this paper we explore a design in between the system and leveraging standard language syntax lets these extremes: first-class dynamic types, building on syntactic types be created, composed, applied, and reused straightfor- support for type annotations and a framework for first-class wardly, so programmers can use these truly dynamic first- pattern-matching, and extend the Grace language leverag- class types to make their programs easier to read, under- ing its existing support for both [2, 23, 37]. The key change is stand, and debug. that run-time expressions may be used as type annotations, and evaluate to first-class pattern objects: any value that CCS Concepts • Software and its engineering → Data flows through a type annotation is dynamically checked by types and structures. the pattern, and types are constructed in a principled, com- positional, and fully-extensible fashion. From most program- Keywords dynamic types, pattern matching, contracts, run- mers’ perspectives, these first-class annotations simply are time checking types; more-advanced programmers can assemble new types ACM Reference Format: either from reusable components or by writing custom pat- Michael Homer, Timothy Jones, and James Noble. 2019. First-Class terns from scratch, drawing on the clear semantics of pattern- Dynamic Types. In Proceedings of the 15th ACM SIGPLAN Inter- matching. national Symposium on Dynamic Languages (DLS ’19), October 20, First-class dynamic types can encapsulate any check that 2019, Athens, Greece. ACM, New York, NY, USA, 14 pages. https: can be expressed in code, can make use of or be parame- //doi.org/10.1145/3359619.3359740 terised by run-time values, can produce warnings or errors, and can manipulate dynamic program state. A pattern al- ready created for use in pattern-matching can be used asa ∗This work is supported in part by the Royal Society of New Zealand Te Apārangi Marsden Fund Te Pūtea Rangahau a Marsden. type, and vice-versa, rather than each being a distinct pro- gram element, and a library of patterns can provide a wealth of different sorts of type checks within the same language ©ACM 2019. This is the author’s version of the work. It is posted herefor or even the same program. Our system requires no addi- your personal use. Not for redistribution. The definitive Version of Record tional annotations, extra sub-languages, or new semantic was published in the 2019 Dynamic Language Symposium, https://dx.doi. or syntactic categories; rather, by adopting existing type- org/10.1145/3359619.3359740. annotation syntax and pattern matching, first-class dynamic DLS ’19, October 20, 2019, Athens, Greece types fit seamlessly into the existing language. A program © 2019 Association for Computing Machinery. ACM ISBN 978-1-4503-6996-1/19/10…$15.00 can be shorter, clearer, and more precise by abstracting away https://doi.org/10.1145/3359619.3359740 DLS ’19, October 20, 2019, Athens, Greece Michael Homer, Timothy Jones, and James Noble checks that would be repeated redundantly or omitted neg- A successful match result behaves like a Boolean true, and ligently, while more directly expressing the intention of the a failure like a Boolean false, so patterns can be used directly programmer simultaneously with providing immediate di- in if statements: agnostics. The resulting system sits at an interesting point in the de- if (negativePattern.match(obj)) then { sign space: less explicit than assertions or Eiffel-style pre- … and post-conditions, less powerful than full-scale Racket- } else { style higher-order contracts, more flexible than transient … dynamic type tests, while retaining immediate, straightfor- } ward, pattern-matching semantics. Any pattern can be used in the match-case construct: 1.1 Contributions The contributions of this paper are: match (obj) : ! • The design of a system of dynamic type-checking based case { x EvenNumber "even" } ! on a pattern-matching framework, with user-defined case { x : Number "other number" } ! patterns. case { x : Green "green" } • An implementation of this system on top of an exist- case {_ ! "not numeric nor green" } ing implementation of the Grace language. • A set of small case studies illustrating different styles The key idea here is that Grace’s blocks (akin to Smalltalk of checking that can be introduced. or Ruby blocks, or lambda expressions) model partial func- tions, rather than total functions as in most other languages. 2 Patterns in Grace This is because single-parameter blocks also implement the match interface — which is why we also call blocks “lambda We build on an object-oriented language called Grace [6], patterns” when they are used in the context of the wider pat- which already supports an object-oriented form of pattern tern subsystem. matching [23]. We will extend this pattern-matching system When a lambda pattern is asked to match another object, to more general applications, but keep the underlying de- the lambda pattern checks that object against the typeor sign intact. pattern annotation on the corresponding parameter — inthe There are three core elements of Grace pattern matching: match-case example, the first block will test the obj against • a “pattern” is an object with a method match return- the EvenNumber annotation. If the annotation matches, the ing a MatchResult object indicating success or failure, whole lambda pattern executes its body and returns an in- where match may have arbitrary user code; stance of successfulMatch; if that annotation doesn’t match, • a “lambda pattern” syntactic construct, where a unary the whole lambda pattern returns a failedMatch. block (an anonymous single-parameter function) con- Each case clause in a match-case construct is a single nects a pattern, a name, and the ability to execute code lambda pattern. The match-case method asks each lambda in the context of the name when the pattern matches; to match in turn until one succeeds, and the body of that and block (only) executes. The overall match-case returns the • a match-case statement, which combines many lambda result of the executed block, or raises an error if no pattern patterns together to provide a typecase-like construct. matched. In more detail, a pattern object has a match method ac- Patterns also support operators & and j, which combine cepting a single parameter, the object to be scrutinised, and two patterns together with conjunctive or disjunctive se- returning a MatchResult object, which is a Boolean with a mantics. These operators parallel the operators used with result property. A successful match acts like true, and m.result Grace types, and in fact all types in Grace are reified as pat- is bound to the matched object, while a failed match acts terns at run time. The type Employee & Dog represents ob- like false. For example, we can write a pattern to check if its jects that are simultaneously Employees and Dogs (accord- argument is negative: ing to standard structural subtyping rules), and the type rei- fied as the pattern Employee & Dog will succeed at match- def negativePattern = object { ing the same objects. method match(o) ! MatchResult { A MatchResult object has a method result containing the < if (o 0) then { object that has been matched, but it is possible for this object return successfulMatch(o) to differ from the one originally provided. Notably, the result } else { of a block used as a lambda pattern is the value returned by return failedMatch(o) the body of the block. Grace also uses this feature to support }}} type casts, but it can be used for specialised patterns that First-Class Dynamic Types DLS ’19, October 20, 2019, Athens, Greece manipulate their targets as well. For example, a pattern like be implemented by a source-to-source rewriting of a pro- the following: gram’s source code (Vitousek et al. [57] describe Reticulated Python’s semantics in a similar way). Given a method: def halfPattern = object { ! method match(o) ! MatchResult { method foo(x : T) R{ if (Number.match(o)) then { … return successfulMatch(o / 2) return x.name } else { } return failedMatch(o) there are two elements to deal with: }}} • The type annotation on the parameter x, T.