The Deturgenchry Programming Language ===================================== WORK IN PROGRESS Version 0.x, sometime in the twenty-tens Chris Pressey, Cat's Eye Technologies Introduction ------------ _Deturgenchry_ is a simple object-oriented language with several distinguishing features. First, Deturgenchry is a _single-assignment_ language. Neither method-local bindings, nor the members of an object, are mutable: once established, they may not be altered. Another, different local binding with a new name must be used, or another instance of the object (with a new value substituted for the given member) must be created. Second, the implicit parameter `self` passed to a method does not refer directly to the object instance on which the method was invoked; it refers to the *method* instance, currently executing. To get to the object instance you have to say `self.object`. And, since this is the currently executing method instance, this is maybe where the local variables live: `self.x`, `self.y`, etc. In other words, in Deturgenchry, `self` is... wait for it... the *current continuation*. Third, unlike most OO languages where only a single implicit `self` parameter is passed, in Deturgenchry *two* parameters are passed implicitly: the `self` and the `other`. This dark symmetry is in honour of modern psychoanalytic mumbo-jumbo or whatever. The `other` refers to the method instance that called the currently executing method. Or rather, the saved continuation the represents the state of the method that called this method, at the point in time at which it called this method. Fourth, there is no `return` statement. Instead, the `other` is continued. Examples -------- Deturgenchry is, like several other languages I've done (Xoomonk, Castile, etc.,) an experiment in _test-driven language design_. I have a rough idea for how the language should work, so I'm going to write a bunch of programs in the as-yet-imaginary language, as tests for an interpreter which doesn't exist yet. Then I'm going to write the interpreter to make the tests pass. -> Functionality "Interpret Deturgenchry Program" is implemented by -> shell command "./deturgenchry %(test-file)" -> Tests for functionality "Interpret Deturgenchry Program" A program consists of zero or more class definitions. When a program is run, a class named `Main` is sought, an instance of it is created, and the nullary method `main` on it is invoked. | class Main { | method main() { | } | } = Null | class Arbitrage { | method main() { | } | } ? No Main class with main() method found | class Main { | method harangue() { | } | } ? No Main class with main() method found | class Main { | method main(n) { | } | } ? Too few parameters passed to method The main method may return control to the operating system (or whatever started running the program) by passing a value to `other`. | class Main { | method main() { | pass other 5 | } | } = IntVal 5 Local variables may be assigned values. | class Main { | method main() { | k = 5 | } | } = Null Local variables may be referenced in expressions. | class Main { | method main() { | k = 5 | pass other k | } | } = IntVal 5 Code in a method may instantiate objects of any class. | class Junk {} | class Main { | method main() { pass other new Junk } | } = ObjVal "Junk" [] Classes don't have to have been defined yet to be referenced. | class Main { | method main() { pass other new Junk } | } | class Junk {} = ObjVal "Junk" [] Classes don't even have to be defined at all, to be referenced. They're assumed to be "plain" classes in this case, with no relationship to any other classes. | class Main { | method main() { pass other new Junk } | } = ObjVal "Junk" [] A class may contain methods. A method may be invoked on an instance in the usual fashion. | class Junk { | method fire(n) { | pass other n | } | } | class Main { | method main() { | o = new Junk | k = o.fire(2) | pass other k | } | } = IntVal 2 The number of actual parameters passed to a method must match the number of formal parameters the method declares. | class Main { | method fire(a,b,c) { pass other b } | method main() { | o = new Main | k = o.fire(4,5,6) | pass other k | } | } = IntVal 5 | class Main { | method fire(a,b,c) { pass other b } | method main() { | o = new Main | k = o.fire(4,5) | pass other k | } | } ? Too few parameters passed to method | class Main { | method fire(a,b,c) { pass other b } | method main() { | o = new Main | k = o.fire(4,5,6,7) | pass other k | } | } ? Too many parameters passed to method A method has access to the currently executing method: `self`. This value is actually a continuation. | class Junk { | method fire() { | pass other self | } | } | class Main { | method main() { | o = new Junk | pass other o.fire() | } | } = ContVal (ObjVal "Junk" []) "fire" [] Methods provide access to the object they're attached to. | class Junk { | method fire() { | pass other self.object | } | } | class Main { | method main() { | o = new Junk | pass other o.fire() | } | } = ObjVal "Junk" [] Deturgenchry is single-assignment. It is not possible to assign to any parameter to the current method, or any variable that has already been bound, or to `self` or `other`. | class Main { | method main() { | o = new Main | o = new Main | pass other o | } | } ? Attempted re-assignment of bound name o | class Main { | method main() { | self = new Main | pass other self | } | } ? Attempted re-assignment of bound name self | class Main { | method main() { | other = new Main | pass other new Main | } | } ? Attempted re-assignment of bound name other The standard if-else construct is a standard enough conditional. There is no boolean type; there is only a special Null value, which represents falsehood. Everything else is truthy. | class Bubkis { | } | class Main { | method main() { | if self { | pass other new Main | } else pass other new Bubkis | } | } = ObjVal "Main" [] A method which doesn't pass anything back to other implicitly passes Null back to other. | class Bubkis { | method fantastic() {} | } | class Main { | method main() { | b = new Bubkis | if b.fantastic() { | pass other new Main | } else pass other new Bubkis | } | } = ObjVal "Bubkis" [] There is a built-in class called StdLib. Instances of this class expose methods to do common useful things, like arithmetic. | class Main { | method main() { | stdlib = new StdLib | pass other stdlib.gt(4, 5) | } | } = Null This is a complicated, contrived, syntactically correct Deturgenchry program. | class binkie { | method foo() { | if self {} else {} | } | method bar(whee, y) { | if y | m = whee.x | else { | m = whee[x = y] | } | pass other m | } | } | class schmoo { | } | class Main { | method main() { | pass other new Main | } | } = ObjVal "Main" [] Grammar ------- Program ::= {ClassDefn}. ClassDefn ::= "class" name "{" {MethodDefn} "}". MethodDefn ::= "method" name "(" [name {"," name}] ")" Statement. Statement ::= Block | Conditional | Transfer | Assignment. Block ::= "{" {Statement} "}". Conditional ::= "if" Expr Statement "else" Statement. Transfer ::= "pass" Expr Expr. Assignment ::= name "=" Expr. Expr ::= RefExpr | "new" name | IntegerLiteral. RefExpr ::= name {"." name} [SetExpr | CallExpr]. SetExpr ::= "[" name "=" Expr {"," name "=" Expr} "]". CallExpr ::= "(" Expr {"," Expr} ")"]. "Discussion" ------------ I believe the germ of the idea that started Deturgenchry was this: Recall that one way to make a recursive anonymous function _x_ is to pass _x_ as the first argument of _x_. The function _x_ then calls this parameter to recurse. This opens up a few possibilities. This first argument can be made implicit and can be called `self`. Further, self need not always be self exactly (like how `self` can be a subclass in OO code.) Further still, `self` could be a continuation which is (semi-)implicitly continued. The existence of `other` could possibly lead to a restriction: each method may contain only *one* call site for any given method. That is, no method may contain more than one call to any given method inside its definition. This should allow the correct "return zone" to be known when continuing the `other`: it is just after the (unique, we now know) location of the call to the current method. Of course, there may be a problem with this if the `other` is stored somewhere and passed to a method that the `other` did not directly call...