[E-Lang] stl-0.8.9k: Syntax Changes
Mark S. Miller
markm@caplet.com
Sat, 20 Jan 2001 23:44:57 -0800
The previous note explained the multi-vow when-catch syntax change, which is
fully upwards compatible from 0.8.9.
I made some changes involving '_', one of which is not upwards compatible,
but probably doesn't effect anyone's code but mine (and I've already fixed
mine).
"_" vs "any"
The non-upwards compatible change was suggested by Ping. (Thanks!) In
0.8.9, the compact notation for inequality regions (based on Xanadu Integer
regions) was
_ > 3
meaning, the region consisting of all integers greater that three. Similarly,
_ >= 3 & _ < 7
means the same thing as
3..!7
all integers from 3 inclusive to 7 exclusive.
These regions can be used as SlotGuards and ValueGuards, effectively
declaring variables and return types to be constrained to lie with this
region, like a Pascal subrange.
Why '_'? Because is sort of looks like a hole. "_ > 3" is sort of like
"for all things that fill in the hole, such that the expression is true". I
though this was synergistic with the other use for '_' in E, the ignore
pattern, which matches anything and binds nothing. This is used wherever
the syntax demands a pattern, and you "don't care":
def _ (a :(_ < 7), _) :(_ > 3) { ... a ... }
effectively defines an anonymous closure -- since it declines to name the
function -- of two parameters, the first of which is bound to parameter
variable "a" after ensuring that it's an integer less that 7, and the second
of which will be ignored. Any value returned will be an integer greater
than 3.
Since "any" is already a SlotGuard and ValueGuard that accepts anything,
Ping suggested using "any" rather than "_" for region formation:
def _ (a :(any < 7), _) :(any > 3) { ... a ... }
Thanks to E's treatment of operators -- they are just sugar for messages,
"any" has no special kernel-level support, other than to be made available
in the initial name space. It is written in E without any special privilege.
There two kinds of hole are clearly different, and I think it's clearer for
them to look different than the same. Thanks Ping!
Lighter Weight Syntax for Anonymous Closures
Among lexical lambda languages, a controversial issue is the need for
macros. Scheme provides very principled macros, and Scheme programmers find
themselves needing to use and invent new macros all the time. Scheme
without macros is considered painful.
Java has no macros, but those who have experienced macros elsewhere find
their lack a constant irritation, although on balance many (including
myself) think Java made the right decision.
Smalltalk also has no macros, and they aren't missed. There was a time in
which I was switching between Smalltalk and a language with macros (well,
sort of. C++ with the C pre-processor). Even then, I rarely felt the lack
of macros in Smalltalk. Dean was in the same situation, felt likewise, and
put his finger on the reason.
Ironically, Smalltalk had a significantly lighter weight closure syntax than
Scheme, so Smalltalk programmers were willing to say, effectively, "lambda"
when they needed to. Smalltalk's square brackets are as light,
notationally, as C's curly brackets, so they could be used, like C's curly
brackets, for all control abstractions. Unlike C, including user defined
control abstractions. Scheme programmers had to spell it out, unless they
invented a macro to say "lambda" for them in this specific pattern, and then
in that one.
E already has language defined C-like curly brackets for a non-extensible
set of control constructs. We also have Scheme-like closures that enable
user-defined control abstractions. Unfortunately, these are also
Scheme-like in the notational pain they impose (or rather, that Scheme would
impose without a macro system). Part of this is on purpose: E's closure
definition syntax encourages closures to be named, since anonymous closures
can't be upgraded. However, closures used as arguments to sequential
control abstractions typically live only during the execution of the control
construct, and therefore, no longer than this turn of the vat. Therefore,
these wouldn't be subject to upgrade anyway.
For example, here's a possible abstraction for gathering together the
elements of a list that pass a supplied predicate:
define select(list, pred) :any {
define result := [] diverge
for x in list {
if (pred(x)) {
result push(x)
}
}
result snapshot
}
No problem here. Now here's a use:
def sublist := select(list, def _(x) :boolean { x%%7 > 3 })
Contrast with the Smalltalk:
sublist := select from: list with: [: x | x %% 7 > 3]
It seems like a trivial difference, but those extra characters means people
will casually use the Smalltalk one as just another control construct, but
not the E one. Likewise, people do not casually use inner classes in Java
for control constructs, even anonymous ones.
The following new sugaring rules accomplish this, works naturally with the
rest of the language, is enabled by the above narrowing of the use of "_",
and (given the above non-upwards compatible change) is fully upward
compatible from 0.8.9.
* When "_" is used as the name in a function/object definition, then the
initial "define" may be left out:
def sublist := select(list, _(x) :boolean { x%%7 > 3 })
* When the initial "define" is left out, if the parts after the "_" are "()
:any", these can be left out as well.
Experience from Smalltalk is that by far the most common form of anonymous
closure as control abstraction argument is the no-argument anonymous
function, also known as the Algol-60 "thunk". For example, a use of a
user-defined while loop in 0.8.9 may look like:
whileLoop(def _() :boolean { ...condition... },
def _() :any { ...body...})
whereas in 0.8.9k, one could write:
whileLoop(_{ ...condition... },
_{ ...body...})
This is sufficiently close to the built in control constructs to feel good.
Cheers,
--MarkM