The Power of Dynamic Multi-Methods (Work-In-Progress Paper)
Total Page:16
File Type:pdf, Size:1020Kb
Mµl: The Power of Dynamic Multi-Methods (Work-In-Progress Paper) Isaac Oscar Gariano Marco Servetto Victoria University of Wellington Victoria University of Wellington Wellington, New Zealand Wellington, New Zealand [email protected] [email protected] Abstract 1 Introduction Multi-methods are a straightforward extension of traditional Most object oriented (and functional1) languages support (single) dynamic dispatch, which is the core of most object only single dynamic dispatch: the code executed by a method oriented languages. With multi-methods, a method call will call is determined by a single argument (the receiver). For select an appropriate implementation based on the values of example, to evaluate a call like x.add(y), the value of x will multiple arguments, and not just the first/receiver. Language be inspected for a definition of an add method (depending support for both single and multiple dispatch is typically on the language, this may be found in a slot/field1 [ ], class designed to be used in conjunction with other object oriented [8], and/or parent object of x [16]) . This approach can be in- features, in particular classes and inheritance. But are these flexible for two main reasons: the value of y is ignored when extra features really necessary? selecting the implementation of add and only the creator of Mµl is a dynamic language designed to be as simple as x can define an add method. possible but still supporting flexible abstraction and poly- Multi-methods [2, 4, 6, 10, 11, 13–15, 17] are one approach morphism. Mµl provides only two forms of abstraction: (ob- to overcoming these limitations: methods can be declared to ject) identities and (multi) methods. In Mµl method calls are dispatch based on the values of multiple arguments. Unlike dispatched based on the identity of arguments, as well as conventional single dispatch, multi-methods can usually be what other methods are defined on them. In order to keep declared outside of the objects/classes of any of their argu- Mµls design simple, when multiple method definitions are ments. Consider for example the following Julia code: applicable, the most recently defined one is chosen, not the add(x:: Number,y:: Number)=x+y most specific (as is conventional with dynamic dispatch). add(x:: Array,y:: Array)= In this paper we show how by defining methods at run- [add(xy[1], xy[2]) for xy= zip(x,y)] time, we obtain much of the power of classes and meta object add(x:: Array,y:: Number)=[add(xe,y) for xe=x] protocols, in particular the ability to dynamically modify the add(x:: Number,y:: Array)=[add(x, ye) for ye=y] state and behaviour of ‘classes’ of objects. add([[1,2],3],4)// evaluates to[[5,6],7] CCS Concepts • Software and its engineering → Ob- Thus a call like add(x;y) will first get the types of x and y, ject oriented languages; Procedures, functions and sub- say T1 and T2, and then execute the body of the add method :: :: :: 0 :: 0 routines; • Theory of computation → Object oriented con- defined with (_ T1,_ T2) (or (_ T1 ;:::; _ T2 ), 0 0 structs; for some super types T1 and T2 of T1 and T2, respectively). In the above example, Array and Number are core language Keywords object oriented languages, multi-methods, dy- types, yet we were allowed to define the various add methods; arXiv:1910.00709v1 [cs.PL] 1 Oct 2019 namic dispatch, meta-object-protocols this is in contrast to conventional single dispatch languages, in which users cannot easily write methods that dynamically ACM Reference Format: dispatch over pre-defined types/classes. Isaac Oscar Gariano and Marco Servetto. 2019. Mµl: The Power of Mµl is a language built around multi-methods as an at- Dynamic Multi-Methods: (Work-In-Progress Paper). Presented at tempt at being even more flexible by eliminating as many Workshop on Meta-Programming Techniques and Reflection (META’19). extra concepts as possible, whilst increasing the flexibility 6 pages. of multi-methods. This work is licensed under Creative Commons Attribution 4.0 International License (CC BY 4.0). META’19, October 20, 2019, Athens, Greece 1 © 2019 Copyright held by the owner/author(s). Since a function or lambda can be thought of as an object with a single ‘apply’ or ‘call’ method. 1 META’19, October 20, 2019, Athens, Greece Isaac Oscar Gariano and Marco Servetto 1.1 The Mµl Programming Language Finally, Mµl supports standard sequence expressions of the 0 0 The language of Mµl2 is designed to be as simple as possible, form e; e , and let expressions of the form x := e; e . We it is an expression based left-to-right call-by-value language present a formalised set of reduction-rules in AppendixA. with the following grammar (where x and µ are identifiers The following example demonstrates the aforementioned and ι is an ‘identity’): features in action: def foo(n){n}; j new j def j ( ) (Expression) e F t M µ e // create three distinct identies 0 0 e; e j x := e; e bar := new; baz := new; qux := new; foo(bar); foo(baz); foo(qux);// evaluates to bar, baz, and qux (Term) t F x j ι (Method) M F µ(p|c){ e} def foo(=bar){ baz}; foo(bar);// Now this evaluates to baz j = (Paramater) p F t t foo(qux);// Still evaluates to qux, as qux , bar (Constraint) c F µ(t) foo(qux);// Evaluates to qux A (ground) term (t) is either a standard variable name (x) or def foo(x| foo2(x)){ foo2(x)}; an identity (ι); during reduction, an x may be replaced by foo(bar);// Still evaluates to baz, as foo2(bar) is undefined an ι. Identities are the only kind of value in Mµl, their only def foo2(=bar){ bar}; intrinsic meaning is whether they are the same or different foo(bar);// now returns foo2(bar), which returns bar (i.e., their ‘identity’). An ι cannot appear in the source code, and can only be created by a new expression, which will Later on we will introduce syntax sugar for natural num- evaluate to a fresh identity that is distinct from any pre- bers, lists, and lambdas. Our examples also use string literals existing ones.; thus identities cannot be forged. of the form "text" which evaluate to an identity ι such that Methods (M) are global and are dynamically installed by an print(ι) will print text. 4 expression of the form def µ(p|c){ e}, or simply def µ(p){ e} We have implemented Mµl , with all the aforementioned when the c is empty. Here µ is the name of the method being features, in Racket [5] by using the ‘brag’ [18] parser gener- defined, however multiple methods may be defined with ator and an encoding of our reduction rules in PLT Redex the same name; the p and c are (comma separated) lists of [9]. We have used this to test that all the code examples parameters and constraints (respectively); and the body of presented in this paper behave as indicated. the method is given by e. The e, p, and c are interpreted in the scope where the def was evaluated, augmented with 2 Abstraction Support appropriate bindings for any pi of form x: i.e. methods are We now show how Mµl is powerful enough to encode various closures, as with traditional lambda expressions. A def ex- language features including mutable fields, ‘value equality’ pression does not reduce to a value/identity, and so cannot (as opposed to reference equality), multiple dynamic dispatch, be used as an argument to a method call or the RHS of a ‘:=’, and multiple inheritance. Similarly to meta-object-protocols, however it can be used as the body of another method3. Mµl allows such features to be used at runtime, allowing state A method call expression is of the form µ(e) and will and behaviour to by dynamically added to whole groups of first evaluate its arguments to identities ι and then reduce to object a time, thus providing support for meta-programming. 0 0 e p B ι , where µ(p|c){ e } is the most recently installed None of this power requires any extensions to Mµl, the two applicable method, and for each pi of form x, p B ι per- core abstractions of identities and methods are sufficient; forms standard capture-avoiding substitution of ι for x.A this is in contrast to most languages were such power is method µ(p|c){ e 0} is applicable to the call µ(ι) whenever provided by specific abstractions and syntactic forms. each p p B ι accepts the corresponding ι, and eachc p B ι is satisfied. When no such applicable method exists, reduc- 2.1 (Mutable) State tion will get stuck. Suppose we want to represent records: collections of named A parameter (p) of form t will accept any value, whereas (mutable) values, we can do this by representing the fields one of form =t will only accept the current value of t.A as methods (which are closures): constraint (c) is of form µ(t) and is satisfied whenever it has def new-point(x,y){//a 'constructor' fora 'record' a matching applicable method: i.e. whenever the call µ(t) is res := new; defined. def get-x(=res){x};//a 'getter' def get-y(=res){y}; // res is likea{x=x,y=y} record 2‘Mµl’ is pronounced like ‘mull’; the μ stands for the ancient Greek word res}; μέθοδος (‘methodos’), meaning pursuit of knowledge.