Some thoughts on the 'reveal' operator

Mark S. Miller markm@caplet.com
Thu, 23 Sep 1999 21:16:01 -0700


At 04:59 PM 9/23/99 , Chip Morningstar wrote:
>The reveal operator ("^") is a new addition to E for me. I thought it was a
>typo when I first saw it. I think I understand what the motivation for 
>this was
>but I also think it's really icky from a human-factors perspective to have to,
>in essence, put an explicit "return" statement in *every* block. (It's also
>problematic to my eyes to have such a significant operation expressed by 
>such a
>small and hard to see character glyph, though I imagine people with a lot of
>Smalltalk experience will be more used to it.) In particular, in the example:

I don't particularly like the human factors myself, but at least on my 
screen&font it's not hard to see.  Poll time: how many of you find this 
hard to see on your system?  What is your system?  Please answer the poll 
privately to me, and I'll summarize for the group.

>    define factorial(n) {
>       ^if (n <= 0) {
>          ^1
>       } else {
>          ^n * factorial(n-1)
>       }
>    }
>
>The reveal on the two inner result expressions seems clear enough, but I found
>the reveal on the 'if' to be quite surprising and counterintuitive, since I'm
>not (yet) used to thinking of statements with a statement-like syntax as
>expressions. This is particularly the case in this example since you need both
>the inner and outer reveals to actually return a result; omitting one but not
>the other in this example yields useless crud:

Yeah.  A Smalltalk programmer would also be puzzled by the "^" on the "if", 
as Smalltalk's "^" is like C's "return".  This difference is why I called 
it "reveal".  Why didn't I use the Smalltalk meaning?  It doesn't 
generalizes poorly, in a way especially bad for E.  If we return, where do 
we return from?  In Smalltalk, methods and blocks were very distinct, and 
methods could never appear lexically nested within another 
method.  Therefore, Smalltalk could get away with saying "'^' returns from 
the method.".  E does not have this asymmetry, so I could not find a 
corresponding control-flow answer that I liked.  In fact, it's good not to 
bring in control flow unnecessarily.  That's the good thing about E's "^".

>    define factorial(n) {
>       if (n <= 0) {
>          ^1
>       } else {
>          ^n * factorial(n-1)
>       }
>    }
>
>*Looks* right, but will be (in C terms) a function returning void. Whereas:
>
>    define factorial(n) {
>       ^if (n <= 0) {
>          1
>       } else {
>          n * factorial(n-1)
>       }
>    }
>
>is just wrong. I would expect it to ..

You caught me.  I took a shortcut I'm still fond of, but you may be able to 
talk me out of it.  A block that does not reveal anything is now considered 
syntactic sugar for a block that reveals null.  Therefore, the expanded 
version of the above two blocks of code would be:

    define factorial(n) {
       if (n <= 0) {
          ^1
       } else {
          ^n * factorial(n-1)
       }
       ^null
    }

and

    define factorial(n) {
       ^if (n <= 0) {
          1
          ^null
       } else {
          n * factorial(n-1)
          ^null
       }
    }

As for the other suggestions:

>  (a) throw an exception (effectively
>crashing the program in this example), (b) silently do something wrong (very
>bad), or (c) get caught by the compiler (which will feel to the programmer 
>like
>one of those pedantic complilation errors ...

I would only consider #c an acceptable alternative.  If you wish to claim 
that my current solution is #b, I'll pout but I won't argue.

>I understand that this is trying to make revelation explicit, presumably so
>that it becomes a deliberate act (for example, so that a function does not
>inadvertantly reveal information that was not meant to be revealed, simply
>because that information happened to be the last thing computed).

That's exactly correct.

>That's a
>laudable goal, but I suspect the mechanism will prove cumbersome. Falling back
>on the custom of using the value of the last expression in a block as the 
>value
>of the block seems to me a more pragmatic design. If we need to have an
>explicit value that denotes a non-result for the "I'm not really returning
>anything" case, that's OK with me.

That was my attitude, and when MarcS first raised this issue, my answer was
to put a "null" expression at the end of a block when you want to hide
things in exactly this way.  However, once I tentatively made this change in
the language (to be available in the upcoming 0.8.5, unless I'm talked out
of it), I then had to make the corresponding change in all my E code.  When
I did so, I found enough exploitable value-leaks in code I thought was
secure that I became a believer in not having the unmarked case reveal a
value. The default case should grant the *least authority*, so to speak.  Of
course, a better notation can still be found.


-----------------------------------------------------------
Due to a bug (possibly at my ISP, but probably in Eudora 4.2.0.58), on 9/15
I lost 18 email messages.  If it's possible for you to check whether you
sent me something on that date, resending it would be great.  Otherwise,
please excuse my non-responsiveness to email you may have sent around that
time.  Sorry for the inconvenience, and thanks.

         Cheers,
         --MarkM