MarcS sez:
>However, this also means that the value returned from a computation is
>always a capability at risk of being sent off into the far distance. In the
>absence of a reveal operation, in the presence of a computation that has no
>particular return value, E's default behavior (of returning whatever
>happened to be the result of the last operation) is a security breach. This
>seems inappropriate for a language with the goal of allowing secure
>computation.
>...
>It is really not acceptable for the default behavior of a capability
>language to be a security breach. Swaddle this problem in as many
>development standards and visual inspections and coding conventions as you
>want, and it still just isn't acceptable. We need to save our security
>auditing strength for the real problems, not for default behavior accidents.
I am completely convinced by the rationale motivating the 'reveal' operator. More specifically, I believe the principle that "reveal is an explicit act, inaction leads to non-revelation" is superior to the converse principle.
What I'm not convinced about is its syntactic realization in the language. If I understand the problem correctly, the primary concern is with values escaping from methods and functions, not so much with values escaping out of nested blocks within a method or function into the enclosing block.
The difficulty with the current 'reveal' syntax is that if I *do* want to reveal a value that is computed inside a nested block, I have to explicitly reveal it at each level of nesting. This seems motivated by purely formal concerns (i.e., ensuring that all blocks are semantically identical regardless of context so we can readily define a lot of stuff in terms of Kernel-E) rather than by considerations of the practical effect of this on the programmer (in practice, the body of an 'if' *is* a qualitatively different beast from the body of a 'define').
From my perspective as a user of the language, either:
define factorial(n) {
^if (n <= 1) {
1
} else {
n * factorial(n-1)
}
define factorial(n) {
if (n <= 1) {
^1
} else {
^n * factorial(n-1)
}
is superior to
define factorial(n) {
^if (n <= 1) {
^1
} else {
^n * factorial(n-1)
}
which gets even uglier in the case of more deeply nested constructs. (Note that in these examples I'm not taking a position about "^" vs. "reveal" or about operator precedence; I'm just talking about block nesting.)
It looks to me like what we've got is the old function vs. procedure dichotomy. If so, then wouldn't it be simpler to just declare things that way like so many languages do, e.g.,
define function factorial(n) {
if (n <= 1) {
1
} else {
n * factorial(n-1)
}
define procedure dosomething(n) {
if (n <= 1) {
mythingie fargulate(n)
} else {
mythingie antifargulate(n)
}
Alternatively, one might just say "function foo" instead of "define function foo", or maybe have some other marker syntax. I'm not arguing strongly for a particular syntax here, just that it's icky (and to my mind unnecessary) to require reveals in all nested blocks in order to get a value out. The only part of this that I'm not real sure of is the cases where a block is used in a value context (e.g., passing a closure as a function parameter).