[e-lang] Announcing Release Candidate 0.8.33g: User Defined Control
Flow
Mark Miller
markm at cs.jhu.edu
Sat Dec 25 15:40:44 EST 2004
At <http://www.erights.org/download/0-8-33/index.html>.
With one exception, this release candidate remains fully upward compatible
from the last official release, 0.8.32e. The only incompatibility is three new
reserved keywords: "datatype", "fn", and "fun". (The one occurrence of one of
these as an identifier in the E source tree has been fixed.)
Note: be sure to read this message in a font where () and {} are
clearly distinct.
The Problem Statement
This release has a new syntactic experiment born of
frustration. Smalltalk, by providing a lightweight syntax for lambda
abstraction, succeeded at enabling programmers to build a variety of
control abstractions for themselves, and to *easily and pleasantly*
use them. E was meant to provide similar power. The following
snippets...
Alan Karp wrote:
>> Second, the syntax
>> __when(foo,thunk{...})
>> looks OK on one line, but I had to read the sugar free version
>> 3 times before I could figure out what went with what.
Andreas Raab wrote:
> Yup, seconded. There is something in the graphical patters which (at
> least for me) when confronted with a line like:
> def fileRcvr := __when(optFileRcvr, thunk {
> makes me actually see
> def fileRcvr := __when(optFileRcvr, thunk )
David Hopwood wrote:
> Perhaps something like (for a 'while' loop):
> while (cond) -> {e} catch prob {ec} finally {ef}
.. and many others indicate that E's current syntax fails at this,
even though E's semantics supports it perfectly well. This is
especially tragic for us, because promise-pipelined event-loop
programming is a new paradigm that needs its own high quality control
flow abstractions. This will only happen if the language users can
easily experiment with new abstractions, like David's suggestion
above. So long as each new control abstraction is a change to the
language syntax, experimentation bottlenecks through me, and our
paradigm will grow too slowly.
Needed: Lightweight Lambda Expressions
Dean pointed at the way out when he wrote:
> [...] If we could figure out a better general syntax for lightweight
> functions, then you could start to use [that] in place of sugar, [...]
It turns out that the pieces of such an answer were to be found in
fossils of old syntax experiments in the e.y file (the yacc grammar
for E), which I've now dusted off. As of this release, if the pragma
switches "anon-lambda" and "lambda-args" are enabled, the production
defining the parenthesized list of arguments in a call is effectively:
parenArgs:
'(' argList ')'
| parenArgs lambdaArg
;
lambdaArg:
sepWord paramList body
;
sepWord:
ID | reserved
| CATCH | ELSE | ESCAPE | FINALLY | GUARDS | TRY
| '->'
;
This means that additional lambda expression arguments can be provided
to a call by placing lambdaArgs after the close paren. (There are some
other experimental cases also enabled by these switches, but I'll
ignore those for now.)
The Interim Expansion
The current interim expansion of this new syntax, explained below, is
not a serious proposal. Rather, it is an expedient shortcut which
preserves the information that a serious proposal would preserve by
other means.
In the current interim expansion, each of these lambdaArgs actually
turns into two arguments: A string representing the sepWord, and a
function made from params and body. For example, the first call to
__if below
__if (3 == 4) then {
println("foo")
} else {
println("bar")
}
has the same expansion as
__if(3 == 4,
"then", thunk { println("foo") } ,
"else", thunk { println("bar") })
This is the wrong way to preserve the sepWord information, and it
creates awkwardnesses in the definitions of the control flow
abstractions, as we'll see below. It also makes explicit invocations
of these abstractions awkward, as seen above, which is what you'd need
to do if you're passing it a function not defined by the above new
syntax. Other possible ways to preserve the sepWord info:
* mangle them into the name of the message name, as Smalltalk does.
* mangle them into the name of the function being invoked.
* treat each pair as a keyword argument, where the sepWord is the
keyword and the function is its value.
I'm going to ignore this issue in the remainder of this message, as
the current interim expansion is good enough to experiment with how
good the above syntax is for *using* (rather than defining) user-defined
control abstractions.
POLA Considerations in Control Flow
Ignoring the particulars of the interim expansion, the semantics it
implements purposely creates two seeming security hazards:
1) The implicitly created functions are of indefinite extent, even
though this exceeds the least authority needed for many of their
uses.
2) The implicitly created functions, like the existing thunk syntax,
implicitly return the value of the last expression evaluated in
their body, with no explicit return, and with no place to put a
result guard. This also exceeds the least authority needed for many
of their uses.
Further, because these functions need to be usable as if they were
inline blocks of built-in control flow constructs, they don't bind
"return". "return" remains bound to whatever it was bound to in the
enclosing context. (e.g., a "return" in the thenExpr of an if
doesn't exit the then-block, it exits the enclosing
method-or-whatever.) (MarcS, sorry it took till now to answer your
question about this -- I was still trying to figure it out.)
As explained in the previous note, we will attempt to express the
needed limits in the control-flow abstractions themselves. Please try
to spot any vulnerabilities that result. Below, the functions
whose names begin with "__..." are the ones we're relying on. (I'm not
suggesting this as a general convention, but we need some distinction
for now.)
The Test Drive
To see how close this gets us to the power Smalltalk programmers are
used to, let's first see how close we can get to defining the existing
control flow sugars as functions:
? pragma.enable("easy-return")
? pragma.disable("explicit-result-guard")
? pragma.enable("anon-lambda")
? pragma.enable("lambda-args")
__if relies on boolean. (Notice the guard on the test parameter below.)
? def __if {
> to run(test :boolean,
> `then`, thenThunk,
> `else`, elseThunk) {
> return test.pick(thenThunk,elseThunk)()
> }
> to run(test,
> `then`, thenThunk) {
> return __if (test, "then", thenThunk) else {}
> }
> }
# value: <__if>
? __if (3 == 4) then {
> println("foo")
> } else {
> println("bar")
> }
# stdout: bar
#
__while relies on __if.
? def __while(`test`, testThunk,
> `do`, bodyThunk) {
> __if (testThunk()) then {
> bodyThunk()
> __while("test", testThunk, "do", bodyThunk)
> }
> }
# value: <__while>
? var i := 0
# value: 0
? __while () test {i < 5} do {
> println(i)
> i += 1
> }
# stdout: 0
# 1
# 2
# 3
# 4
#
Of course, this doesn't capture all the power of the existing
while-loop: it can't support break and continue.
The __for loop is the first interesting experiment, because at some
point it turns control over to the collection's iterate method. What
POLA assurances can it enforce without relying on the proper behavior
of the collection? Can it avoid the hazards identified at
<https://bugs.sieve.net/bugs/?func=detailbug&bug_id=125606&group_id=16380>?
? def __dynamicExtent := <import:org.erights.e.facet.__dynamicExtent>
? def __for(collection,
> `do`, assocFunc) {
> def noReturnFunc(k,v) {
> assocFunc(k,v)
> }
> __dynamicExtent (noReturnFunc) as func {
> collection.iterate(func)
> }
> }
The source of __dynamicExtent is essentially unchanged from the
previously posted attachment, but is attached again anyway.
? __for ('a'..'d') do k,v {
> println(`$k => $v`)
> }
# stdout: 0 => a
# 1 => b
# 2 => c
# 3 => d
#
Again, this doesn't capture all the power of the existing for-loop. But when
you need something that doesn't quite fit the built in sugars, this shows that
you can spin your own, and have the result be usable by yourself and others.
Here's an implementation of David's suggested eventual-while loop, where this
implementation uses no built-in control flow sugars. In particular, it uses
the three argument form of '__when' MarcS suggested, rather than the built-in
'when' sugar. (Because of our cheezy way our interim expansion sepWords, this
case is now a five argument form of __when.)
__whenWhile relies on __when and __if.
? def __whenWhile(`test`, testThunk,
> `->`, bodyThunk,
> `catch`, handlerClosure,
> `finally`, finallyThunk) {
> def theLoop () {
> __when (def t := testThunk()) -> {
> __if (t) then {
> bodyThunk()
> theLoop()
> } else {
> finallyThunk()
> }
> } catch prob {
> handlerClosure(prob)
> finallyThunk()
> }
> }
> theLoop()
> }
# value: <__whenWhile>
David's originally suggested sugar syntax, with realistic indentation
while (cond) -> {
e
} catch prob {
ec
} finally {
ef
}
would instead be
__whenWhile () test {cond} -> {
e
} catch prob {
ec
} finally {
ev
}
None of the awkward revocation shown in David's latter message,
<http://www.eros-os.org/pipermail/e-lang/2004-December/010326.html> is
needed, or would do any good. Since David's revocation logic is within
the whenWhile, the program is relying on it anyway. The program can
and should instead simply rely on __whenWhile not to abuse its
arguments, or to give anything else (outside the understood reliance set)
access to these arguments. I believe the above __whenWhile is reliable
in this sense.
The release also contains two capEdits. capEdit.caplet is the old one, using
the built-in when sugar. In capEdit-synexp.emaker (For "syntax experiment"),
the first three of these have been replaced with sugar-free versions using
__when via the syntax presented here. Diff 'em and weep.
Finally, here's the last example at
<http://www.erights.org/elang/examples/when-examples.e.html> recoded
in this new style, using just __when:
def myCurrency := account <- getCurrency()
def servCurrency := service <- getCurrency()
def price := service <- getPrice()
def payment := __when.allFulfilled ([myCurrency,
servCurrency]) -> {
if (servCurrency == myCurrency) {
account <- makeOffer(price)
} else {
def option :=
combio <- makeOption(myCurrency, servCurrency, price)
option <- exercise(account <- makeOffer(option <- getPrice()))
}
} catch prob {
println("service failed: " + prob)
}
service <- perform(payment)
Please take this syntax experiment out for a spin and see how it flies.
Enjoy 0.8.33g!
--
Text by me above is hereby placed in the public domain
Cheers,
--MarkM
-------------- next part --------------
A non-text attachment was scrubbed...
Name: __dynamicExtent.emaker
Type: e
Size: 3221 bytes
Desc: not available
Url : http://www.eros-os.org/pipermail/e-lang/attachments/20041225/cef2c158/__dynamicExtent.bat
More information about the e-lang
mailing list