Here I present a short (one-page) class in Bicicleta for exact arithmetic on rational numbers, followed by a ten-page explanation of how it works. It's loosely based on the example at the beginning of chapter 2 of SICP.
It is intended to stand alone, comprehensible without reference to any of the previous Bicicleta posts I have made on kragen-tol and kragen-hacks. Unfortunately, I still don't have a runnable Bicicleta system, which has several effects on this class: - it probably has some bugs that will be obvious when I try to run it; - I did not write unit tests, since I would not be able to run them; - the textual source code does not have a full complement of example values in it. - it's about fractions, which are boring, instead of something cool like colors or 3-D solid models or web sites. The textual syntax in this mail message is not quite the syntax you will see when you're editing the program. Because it's so important to be able to exchange and discuss program fragments in purely textual media like email, I plan to fully support copy and paste in this textual format from the Bicicleta environment, without losing any semantics; and because it's so important to integrate with source-control systems, I plan to use the textual format as the on-disk persistent storage format for Bicicleta programs. But within the Bicicleta environment itself, the UI for interacting with the program is not purely textual. (In itself, this is not a large difference from traditional IDEs like Eclipse.) The other important thing to know is that Bicicleta, like Haskell, pervasively uses lazy evaluation: no expression is evaluated until its value is needed. Things that embarrass me are marked with "***". The Whole Class --------------- First, here's the class all at once. I've followed it with a series of piece-by-piece explanations. rational = prog.sys.number {self: numer = 1 denom = 2 new = {op: arg1 = 6 arg2 = 9 g = op.arg1.gcd(op.arg2) numer = op.arg1 / op.g denom = op.arg2 / op.g '()' = (op.numer * op.denom).if_not_error( self { numer = op.numer, denom = op.denom }) } show = "{numer} <hr> {denom}" % self # presentational form definition to_rational = {op: arg1 = 2 '()' = prog.if( op.arg1.denom.is_ok -> op.arg1 op.arg1.is_a(prog.sys.integer) -> self.new(op.arg1, 1) else = prog.error() ) } rational_binary = {m: arg1 = prog.sys.number.'+' '()' = m.arg1 {op: 3, other = self.to_rational(op.arg1)} } # prog.sys.number.'+' returns 'result' unless it's erroneous, so we # override 'result' '+' = self.rational_binary(prog.sys.number.'+') {op: result = self.new( (self.numer * op.other.denom) + (self.denom * op.other.numer) self.denom * op.other.denom ) } # The theory is that the standard number types' implementations of # '+', '-', and the like, if the normal implementation fails with # an error, will try passing themselves to 'reverse +', 'reverse # -', and so on, instead. The override here is to stop the # recursion. 'reverse +' = self.'+' {op: '()' = op.result} negate = self.new(-self.numer, self.denom) '-' = self.rational_binary(prog.sys.number.'-') {op: result = self + op.other.negate } # This should not call '-', because if it does, and '-' is somehow # broken, we get infinite recursion. 'reverse -' = self.'+' {op: '()' = self.negate + op.other} '*' = self.rational_binary(prog.sys.number.'*') {op: result = self.new(self.numer * op.other.numer, self.denom * op.other.denom) } 'reverse *' = self.'*' {op: '()' = op.result} recip = self.new(self.denom, self.numer) '/' = self.rational_binary(prog.sys.number.'/') {op: result = self * op.other.recip } 'reverse /' = self.'+' {op: '()' = self.recip * op.other} # Equality is a really tricky operation. Here we're implementing # numerical equality, but I'm not sure that's the right thing. '==' = self.'+' {op: '()' = (self.numer * op.other.denom) == (self.denom * op.other.numer) } } rational = prog.sys.number {self: ... } --------------------------------------- This says "'rational' inherits from prog.sys.number, with the following differences: ...". The identifier "self" as the first thing in the {} and followed by a ":" gives a name by which methods in 'rational' can refer to the object on which they are called. (Sometimes I refer to methods as 'fields' in what follows.) 'prog' is the name given to the top-level namespace of the program. Several of the fields introduced inside this expression do not exist in prog.sys.number. (I'm not sure exactly which ones, but several of them.) In Abadí and Cardelli's ς-calculus, introducing new fields in an override expression like this is against the rules, but I am disregarding this restriction. numer = 1, denom = 2 -------------------- This defines two methods on the 'rational' object; one of them returns 1, and the other returns 2. These are intended to be overridden in other objects derived from 'rational', but they serve as example values that allow the "archetypal" or "prototypical" rational object to be viewed as a real rational number, in this case 1/2. This allows the environment to constantly display the effects of your changes to the code in real time. new = {op:...} -------------- This defines a field (or method) whose value is another object, which is intended as a constructor for new 'rational' objects. Within the body of its definition, the name 'self' still refers to the 'rational' object on which 'new' was called, and 'op' refers to the 'new' object itself. arg1 = 6 arg2 = 9 This defines two fields whose use becomes apparent later. g = op.arg1.gcd(op.arg2) This is syntactic sugar for the following definition: 'g' = op.'arg1'.'gcd'{'arg1' = op.'arg2'}.'()' by way of the following translations: 1. It is allowed to leave off the '' around the name of a field when the field name contains only alphanumeric and underscore characters; (The '' allow the use of any arbitrary characters in field names.) 2. In general x(y) is syntactic sugar for x{y}.'()'. Here 'x' is some object, 'y' specifies some set of overrides on x to make a derived object, and .'()' extracts the '()' field of the resulting object. 3. Positional arguments in an override expression are treated as overrides for the fields 'arg1', 'arg2', and so on. Note that rule 3 means that you can write 'rational.new(2, 5)' to mean 'rational.new{arg1=2, arg2=5}'. This says that the 'g' method on the 'new' object does the following: 1. extracts the 'arg1' field from the 'new' object on which it is called; 2. extracts the 'gcd' field from that 'arg1' object; 3. extracts the 'arg2' field from the same 'new' object; 4. creates an object derived from the 'gcd' object in step 2 by overriding its 'arg1' method to return the object from step 3; 5. extracts the '()' field from the resulting object. 'gcd' is a method defined on integer objects; its '()' field contains its "return value", which is the greatest common divisor of the number on which it is called and its argument. In this case, it is 3, but in an object derived from 'new', arg1 and arg2 may be overridden, in which case 'g' will have a different value. Here is the current sugar-free notation for this field definition: g = op.arg1.gcd{arg1 = op.arg2}.'()' I used to write it as this instead: op.g = op.arg1.gcd{arg1 = op.arg2}.'()' This is more self-contained; but I concluded that it made more sense to provide a single "self-name" for all the methods in a particular expression, and I think the above was confusing to read, because most readers did not guess that the first occurrence of 'op' introduced a new binding for 'op' rather than referring to an existing binding. If I recall correctly, in the notation of Abadí and Cardelli, it is written like this: g = ς(op) op.arg1.gcd{arg1 <= op.arg2}.val which I usually expand in ASCII like this: g = sigma(op) op.arg1.gcd{arg1 <= op.arg2}.val where we write 'val' for '()' because they don't have a notation for non-alphanumeric method names. Note that the above does not constrain arg1 and arg2 to be integers; arg1 just has to have a 'gcd' field which has a '()' field. numer = op.arg1 / op.g denom = op.arg2 / op.g These divide the arg1 and arg2 by the gcd, giving 2 and 3 respectively. They are syntactic sugar for definitions like this: numer = op.arg1.'/'(op.g) which in turn is sugar for 'numer' = op.'arg1'.'/'{arg1 = op.'g'}.'()' Any sequence of characters from this sequence will be interpreted in this fashion as an infix operator: [EMAIL PROTECTED]&*-+=<>?/\| So you can define a ** operator or a += operator or a @!@@ operator by defining fields on your objects with those names. There is no precedence among these infix operators, and they all associate from left to right; but, probably needless to say, the built-in "." and override syntax bind more tightly than these infix operators, and the separation between items expressed by "," or a line break binds more loosely. '()' = (op.numer * op.denom).if_not_error( self { numer = op.numer, denom = op.denom }) This field, by convention supported by syntactic sugar, holds the "return value" of 'new' --- so if you say 'rational.new(3, 4)' you are getting the contents of this '()' field. The 'if_not_error' method is defined on all objects. For normal objects, it just returns its argument: if_not_error = {op: '()' = op.arg1} But for error objects, which result from calls to nonexistent methods (among other things), it returns the error object itself: if_not_error = { '()' = self } The intent of the if_not_error call is to catch cases where op.numer, op.denom, or both, are error objects, or where they are not the sort of thing you can use as rational numerators or denominators. If op.numer is an error object, its '*' method will just return another error object; and the intrinsic '*' method defined for integers will also return an error if passed an error object as an argument. Finally, if op.numer is some kind of thing that doesn't have an '*' method, or whose '*' method cannot accept op.denom as an argument, we will get an error object reporting this, instead of a rational number. (Being able to multiply together numerators and denominators is a necessary condition for arithmetic on rational numbers, and it should catch nearly all cases where the wrong thing was passed in.) Error objects propagate along dataflow paths, as in VisiCalc and other spreadsheets, rather than control-flow paths, as exceptions do in CLU or Java. This is in part because the control-flow paths in a lazy program are very hard to understand, but my experience with such a mechanism in Wheat leads me to believe that this is a reasonable approach for an imperative language as well, as long as there's an escape hatch for error values evaluated in void context "for effect", so they don't get lost. This expression is a horizontal layout form: self { numer = op.numer, denom = op.denom } It's exactly equivalent to this: self { numer = op.numer denom = op.denom } This constructs an object similar to the one on which 'new' is being called, but with different 'numer' and 'denom' fields. At one point in the past, I argued that constructions like this, where the kind of object you instantiate covaries with the kind of object you were operating on, were dead wrong. I was wrong. This points out that you don't have to construct rationals through the 'new' method. You could also say "rational { numer = 3, denom = 4 }" and have a perfectly usable 'rational' object. The reason I'm providing the 'new' method is that it provides a useful level of indirection. "rational { numer = 3, denom = 4 }" cannot return an error object, nor rewrite numer and denom to lowest terms the way 'new' does; and I found experimentally that it's quite easy to override the wrong fields and introduce a subtle bug. It's worth noticing that you could use 'new' to construct rationals from any kind of object that supports 'gcd', '/', and '*' methods that interact in the expected way; and, if they support the other ways that numerators and denominators are used in this class (adding their products together, negating them, testing for equality), they will work, too. For example, I think you could use this class unchanged to support rational expressions of polynomials. show = "{numer} <hr> {denom}" % self ------------------------------------ By convention, 'show' defines an HTML representation for the number using the '%' method of strings, which uses reflection to extract the fields named inside {} and interpolate them into a string. Traditionally, programming languages have ways to render their objects into ASCII strings, for debugging purposes if nothing else, but I think HTML has supplanted ASCII text today. "show" is what the Bicicleta environment uses to display the current values of the objects you're editing. I anticipate that for many classes, "show" or something like it will suffice as a user interface. *** This is not the right way to generate HTML. I probably want something like Nevow stan or MochiKit.DOM, such as "prog.html(self.numer, prog.html.hr, self.denom)", but I need more experience to design that. '+' = self.rational_binary(...) {op: ...} ----------------------------------------- '+' = self.rational_binary(prog.sys.number.'+') {op: result = self.new( (self.numer * op.other.denom) + (self.denom * op.other.numer) self.denom * op.other.denom ) } This defines a method for '+', which inherits from self.rational_binary(prog.sys.number.'+'), which (as we'll see below) inherits from prog.sys.number.'+'. Rather than defining '()', which is what is actually used in an expression like "r1 + r2" (remember, that rewrites to "r1.'+'{arg1=r2}.'()'") we define 'result', which the '()' we inherit from prog.sys.number.'+' will return under most circumstances. Due to the lack of operator precedence in Bicicleta, we can't just write "numer * other.denom + denom * other.numer"; that would parse as "((numer * other.denom) + denom) * other.numer". So there's a set of parentheses on the right to make it parse correctly, and another one on the left for symmetry and clarity. 'other', as explained below, comes from 'rational_binary' and contains a rational-number version of the argument, whether or not the argument was originally a rational number. to_rational = {op: ... '()' = prog.if(...)} ------------------------------------------- to_rational = {op: arg1 = 2 '()' = prog.if( op.arg1.denom.is_ok -> op.arg1 op.arg1.is_a(prog.sys.integer) -> self.new(op.arg1, 1) else = prog.error() ) } Most of the rest of the class is concerned with arithmetic. It's desirable to be able to mix rational numbers with integers in arithmetic; so this method returns a rational number, given either a rational number or an integer. The '->' method is defined for all objects; it returns an 'association' containing the object it was called on and its argument. 'if' is defined at 'prog', the top level; it implements a multi-way conditional similar to Lisp's "cond". You pass it any number of associations as arguments, and it iterates over them until it finds an association whose left-hand side is true, at which point it returns the right-hand side. If none of them are true, it returns 'else', in this case, an error object. (*** I probably ought to provide an error message.) Note that the definition of 'if' in this fashion requires access to the whole list of positional parameters, not just particular individual positional parameters. Because the language is lazy, 'if' can be just an ordinary function rather than a language special form. It is unfortunate that I have to write "prog.if" rather than "if" in the textual syntax. This is because only the objects textually enclosing an expression are bound to names in its scope; in this expression, if 'rational' is evaluated at the top level 'prog', the environment consists only of 'prog', 'self', and 'op'. I anticipate that the Bicicleta environment will abbreviate these names for display where doing so will not lead to reader confusion: to_rational = {op: arg1 = 2 '()' = if( arg1.denom.is_ok -> arg1 arg1.is_a(sys.integer) -> new(arg1, 1) else = error() ) } 'is_ok' is a method defined on all objects; it returns true for all objects except for error objects. If arg1 is a rational number, then unless its denom is an error object, arg1.denom.is_ok will be true. But for almost all objects other than rational numbers, arg1.denom.is_ok will be false. This is an example of using a 'protocol test' rather than an is-a or isinstance test. (See my web page, "isinstance considered harmful".) This leads to looser coupling and more maintainable systems. Unfortunately I'm not sure how to do a protocol test to distinguish, say, integers, which can be converted to exact rational numbers in the way suggested above, from, say, floating-point numbers, which cannot. Probably it will be clear how to do this after I've implemented some of the more basic kinds of numbers. But in the mean time, I've fallen back on "op.arg1.is_a(prog.sys.integer) -> self.new(op.arg1, 1)". rational_binary = {m: arg1 = prog.sys.number.'+' '()' = m.arg1 {op: 3, other = self.to_rational(op.arg1)} } The methods that want to convert something else to a rational number are all binary arithmetic methods of one kind or another, and the thing they want to convert to rational is their argument. The trouble is, for other reasons explained below, they need to inherit from their corresponding methods in prog.sys.number, and because Bicicleta has neither multiple inheritance nor super and therefore we cannot simply implement "other = to_rational(arg1)" as a mixin. However, this method demonstrates that it's straightforward to get mixin-like functionality from a function. This function adds arg1=3 and the "other" method to its argument, and returns it. In retrospect, this method probably didn't make the code much simpler, but I've left it in because it demonstrates this important point about the need for mixins. *** Perhaps the "try to coerce to my type" behavior should be inherited from prog.sys.number operations instead of implemented in every numeric type. At this point we have all the pieces to know that rational.'+'() is 7/2, because the default argument provided by rational_binary is 3. *** I'm not sure about whether I should be providing reasonable default arguments like this, which makes the code clearer when you're editing it and seeing operational results, or defaulting to error values so that when you use the class you get error messages instead of silent wrong answers. I'm thinking that perhaps the first is better during initial development, while the second is better later on. 'reverse +' = self.'+' {op: '()' = op.result} Earlier I said that prog.sys.number.'+' would return 'result' under "most circumstances". I meant that it does the following: '+' = {op: '()' = op.result !! op.arg1.'reverse +'(self)} The '!!' error-handling operator comes from Wheat. It's like '||', but for non-broken-ness rather than truthiness. In normal objects, it's defined as follows: '!!' = { '()' = self } But in error objects, it's defined as follows: '!!' = {op: '()' = op.arg1 !! self.augment_err("trying to recover from", op.arg1) } So, if 'result' isn't an error, '+' returns it; if it is, it tries arg1.'reverse +'(self), and if that isn't an error, it returns that; and if they're both errors, it returns an error containing both errors. *** That won't actually work as written because self.augment_err returns an error object. It should probably be written out as an if. In this case, the way this works is that in "1 + rational.new(1, 2)", we try 1.'+', which presumably will try and fail to coerce the rational number to an integer; then it will fall back on "rational.new(1, 2).'reverse +'(1)". At first I tried this: 'reverse +' = self.'+' This works in the normal case, where you're trying to add two rational numbers, or a rational number and an integer (in either order). But if there's an error (say, if you try to add 0.5 to a rational number) then our 'reverse +' operator goes right ahead and tries to call 0.5.'reverse +' again. Which is OK, because that fails, and we get a relatively sensible error. But if there's some kind of bug in rational.'+' that makes 'result' always an error for two particular rationals, then we'll fall into an infinite recursive loop trying to add them, as they futilely swap back and forth trying to find a configuration that works. This will result in an error message that is considerably less helpful than it could be. So we inherit 'reverse +' from rational.'+', but we override '()' to return 'result' directly instead of trying to reverse places again. negate = self.new(-self.numer, self.denom) This returns a negative version of the number (unless it's already negative, in which case it returns a positive version). Its value is -1/2 in the prototypical rational object. *** The "-" in here doesn't fit into the syntactic structure I described earlier for infix operators, and as a consequence, there's no obvious way to override it. I am inclined to write this as "self.numer.negate" instead and banish prefix operators completely from the language. '-' = self.rational_binary(prog.sys.number.'-') {op: result = self + op.other.negate } 'reverse -' = self.'+' {op: '()' = self.negate + op.other} '-' overrides 'result' because '()' inherits a fallback to 'reverse -' from prog.sys.number.'-', and 'reverse -' overrides '()' rather than 'result' in order to avoid falling back. I inherited 'reverse -' from self.'+' to avoid having to write out the rational_binary or to_rational conversion again (and potentially forgetting). This has the perverse side effect that 'reverse -' inherits an unused 'result' field containing the sum of 'self' and 'other'. The 'reverse -' could still lead to an infinite recursive loop if someone decided to implement '+' in terms of '-' (perhaps in some other class), but it's not likely. *** It would be nice if new programmers didn't have to deal with this level of subtlety just to define a new numeric type. They don't in Python. Maybe a level of indirection like "rational.new" could help? Maybe something you could wrap around the whole class, like "rational.rational_binary" wraps around a single operation. '*' = self.rational_binary(prog.sys.number.'*') {op: result = self.new(self.numer * op.other.numer, self.denom * op.other.denom) } 'reverse *' = self.'*' {op: '()' = op.result} Perhaps I should define a 'commutative_reversed' method so that I could say 'reverse *' = self.commutative_reversed(self.'*'). recip = self.new(self.denom, self.numer) '/' = self.rational_binary(prog.sys.number.'/') {op: result = self * op.other.recip } 'reverse /' = self.'+' {op: '()' = self.recip * op.other} Follows the same patterns as previously described for '-'. '==' = self.'+' {op: '()' = (self.numer * op.other.denom) == (self.denom * op.other.numer) } This definition is tricky because there are lots of different kinds of equality: numerical equality, structural equality, and identity equality are the ones we have to worry about here. Here I've picked numerical equality as the definition for rational.'==', with the consequences that "rational.new(2, 1) == 2" is true, and "rational.new(2, 1) == 0.1" returns an error instead of false. If our 'new' constructor (or gcd?) were smart enough to ensure that denom was never negative, perhaps we could reduce this to "(numer == other.numer) && (denom == other.denom)". *** '==' probably should use the same coercion techniques as '+' and family if we're interested in numerical equality, so that we can test "2 == rational.new(2, 1)". The Whole Error Object Protocol ------------------------------- Bicicleta has Wheat-style error objects, instantiated with prog.error(), but they're just ordinary objects that implement only a few methods: - is_ok = false (true for other objects) - if_not_error = {'()' = self} (arg1 for other objects) - '!!' = {op: '()' = op.arg1 and a bit more} (self for other objects) - show: something handy with hyperlinks - get_error_info: returns the information about the object There are some other methods I haven't really defined, like the "augment_err" mentioned above. Possibly I should hide them behind an "as_error" method. Additionally, when you call a nonexistent method on an error object, the error object returned isn't a fresh "nonexistent method called" error object; it's the original error object, augmented with information about being propagated through that method call. This is achieved through some facility I haven't defined yet, analogous to Smalltalk's "doesNotUnderstand:" or Python's "__getattr__". Wheat's error objects modify themselves in place when they are passed around. This became confusing in practice; in Bicicleta, error objects have a 'augment_err' method that returns a copy of the same error object, but with more trace information hanging off of it. Is Succinctness Power? ---------------------- If you compare the above to the version that heads SICP's Chapter 2, you will notice that it is substantially longer, by a factor of two to four. I love brevity less than Paul Graham or Arthur Whitney, but I certainly recognize brevity as precious, and I was dismayed when I first realized how verbose Bicicleta code was. Since that dismay, I have improved it somewhat, and now I think it's roughly competitive with Scheme. Most of the extra length comes from the hassles of integrating smoothly into an existing arithmetic system, which isn't even possible in Scheme (in the abstract, that is --- I think it's possible in, say, RScheme or PLT Scheme.) It's still a somewhat fluffier language, mostly because it does many things by name that Scheme does positionally. Here's a version of the 'rational' class that's missing all the extraneous code, focusing only on the basics: rational = {r: numer = 1 denom = 2 new = {op: arg1 = 6 arg2 = 9 g = op.arg1.gcd(op.arg2) numer = op.arg1 / op.g denom = op.arg2 / op.g '()' = (op.numer * op.denom).if_not_error( r { numer = op.numer, denom = op.denom }) } show = "{numer} <hr> {denom}" % r '+' = {op: '()' = r.new( (r.numer * op.arg1.denom) + (r.denom * op.arg1.numer) r.denom * op.arg1.denom ) } negate = r.new(-r.numer, r.denom) '-' = {op: '()' = r + op.arg1.negate} '*' = {op: '()' = r.new(r.numer * op.arg1.numer, r.denom * op.arg1.denom)} recip = r.new(r.denom, r.numer) '/' = {op: '()' = r * op.arg1.recip} '==' = {op: '()' = (r.numer * op.arg1.denom) == (r.denom * op.arg1.numer)} } That's 26 lines, 812 characters. And here's a version abbreviated in the way I think things will normally be abbreviated for display, where the namespace name is omitted for uniquely-named methods: rational = {r: numer = 1 denom = 2 new = {op: arg1 = 6 arg2 = 9 g = arg1.gcd(arg2) numer = arg1 / op denom = arg2 / op '()' = (op.numer * op.denom).if_not_error( r { numer = op.numer, denom = op.denom }) } show = "{numer} <hr> {denom}" % r '+' = { '()' = new( (numer * arg1.denom) + (denom * arg1.numer) denom * arg1.denom ) } negate = new(-numer, denom) '-' = {'()' = r + arg1.negate} '*' = {'()' = new(numer * arg1.numer, denom * arg1.denom)} recip = new(denom, numer) '/' = {'()' = r * arg1.recip} '==' = {'()' = (numer * arg1.denom) == (denom * arg1.numer)} } That's 26 lines, 720 characters. By comparison, the SICP version is 35 lines, 788 characters, but it has some duplication of function; if I factor it and format it in an analogous way, it's 18 lines, 710 characters. Some parts of the Scheme version are actually more verbose than their Bicicleta counterparts, which is why it's possible to come so close. Consider: negate = new(-numer, denom) negate = r.new(-r.numer, r.denom) negate = self.new(-self.numer, self.denom) negate = self.new(self.numer.negate, self.denom) (define (neg-rat x) (make-rat (- (numer x)) (denom x))) '-' = {op: '()' = r + op.arg1.negate} (define (sub-rat x y) (add-rat x (neg-rat y))) So at this point I no longer have a lot of Scheme envy. Scheme still wins by a little bit most of the time on brevity-in-the-small and clear applicative-order control flow, but I think Bicicleta will win on infix notation, named arguments, better namespace management, runtime inspectability, and easier data structuring. Compared to minimal standard schemes, Bicicleta will also win on late binding and composability --- the SICP example rational class can only use built-in numbers for its numerator and denominator. The biggest recent improvement in brevity was not having to write the self-name on every method that used it: self.negate = self.new(-self.numer, self.denom) Things Not Shown ---------------- There are a couple of things that are part of the Bicicleta design that haven't made an appearance here. The biggest one is editable presentation forms. When I'm editing my source, I don't want rational numbers to display as "rational.new(3, 4)"; I want them to display like this: 3 --- 4 But not a hokey dashed line made out of hyphens --- a solid line, with a minimal amount of space above and below it. I might want to switch to "text view" to see how to make another one (the big advantage of text is that it makes the gulf of execution quite small), but I should be able to change the 3 to a 5 without going to that extreme. The handling of side effects is something I have given some thought to, but which doesn't show up here. My plan is to make a special kind of function called a 'procedure'; to only allow procedures to be called from procedures; to cause UI elements to invoke procedures; to cause the 'main' function of a standalone program to invoke a procedure; to provide special procedures that implement iteration of a procedure and sequencing of multiple procedures; to use 'if' to implement a choice between N procedures (it just returns the procedure it wants to call); and, generally, to record the last value returned from a procedure so it can be inspected without being re-executed. Another, much smaller, item is that "x[y]" is syntactic sugar for "x.'[]'(y)", as in Wheat; this is used for indexing into data containers. This (plus conventions for escaping in strings) completes the demonstration of the textual language grammar.