Topic: Proposed "with" statement and expression


Author: "Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com>
Date: 1997/05/10
Raw View
Looking for comments...

I've had an idea for passing "context" information into functions,
bypassing the normal parameter-passing mechanism. It is roughly
symmetric with exception handling, which passes information out of
functions, bypassing the normal return mechanism. And in fact it would
use some of the same internal mechanisms.

Here is a suggested statement syntax:

 with ( expression ) statement

This would record the type and value of the expression, and then execute
the statement, during which the value of the expression may be
interrogated by type using:

 default < type-id > ( expression )

(I've chosen "default" merely to avoid inventing another keyword.) This
would search (perhaps by walking the stack) for the most recently
entered "with" statement whose expression is of the specified type (or
more derived type), and returns the corresponding value, without
evaluating the expression in parentheses. If no matching "with"
statement could be found, the result would be the value of the
expression in parentheses, converted to the specified type. In addition,
the expression could be a throw-expression, in which case the lack of a
matching "with" statement would throw the specified exception.

One likely use of this would be to implement continuation-style
exception handling, where a "with" statement is used to specify what to
do in case of a particular error. The value could be an enum that
chooses among different alternatives:

 enum overflowmode { wrap, clamp, trap };

 int add_ints(int x, int y) {
     int z = x + y;
     if ((x > 0 && y > 0 && z < 0)
             || (x < 0 && y < 0 && z > 0)) {
         switch (default<overflowmode>(wrap)) {
             case trap: throw overflow();
             case clamp: z = z < 0 ? MAX_INT : MIN_INT;
             }
         }
     return z;
     }

 with (clamp) a = add_ints(b, c);

The value could also be a pointer to a function that would be called in
the event of an error, for more detailed control.

The whole point, however, is that the value would be associated with the
type throughout the execution of the subsidiary statement, unless hidden
by another "with" statement using the same type.

It would also be nice to have a "with-expression", as well as a
"with-statement". This could, I suppose, be written with "with" as a
binary operator:

 expression with expression

where the right-hand expression is the value that is recorded.

 a = add_ints(b, c) with clamp;

This reads nicely, but the right-hand side would have to be evaluated
before the left-hand side, which is odd. The opposite, however, looks
pretty opaque.

I've been writing code for a few years in an environment in which
objects, including tasks, can have properties (name-value pairs)
attached to them. This has been a useful technique for lots of purposes:

1) Overriding the default buffering algorithm when opening a file. Such
minutiae are not the sort of thing one would want every open() call to
specify with a parameter.

2) Registering a progress-reporting callback function, so that
time-consuming operations (such as copying files) can provide a status
display, in environments where that is appropriate.

3) Telling a file creation function whether or not to create intervening
non-existent subdirectories along the path.

4) Specifying a time limit on a complex operation, where the
lowest-level part of the operation is responsible for checking the time
limit.

5) Supplying a buffer in which additional error information can be
captured when an exception is thrown.

6) Overriding the default font when writing to a window.

7) Specifying a default directory for looking for files.

The technique is useful any time the information you want to pass from a
high-level function to a low-level one meets one of the following
criteria:

A) The information is used sufficiently rarely that you don't want to
burden the function with a rarely-used parameter.

B) The information might only be meaningful in some circumstances,
depending upon what low-level driver happens to be invoked. Other
drivers could simply ignore the information.

C) The information represents context to some large and complex
operation, and you don't want the inefficiency of passing the
information all over the place inside the operation by parameter.

One common implementation of exception handling involves setting up a
chain of exception contexts, including all functions that are involved
in exception handling. The contexts contain pointers to static tables
that show what destructors need to be called during stack unwinding, and
what exception types may be caught. The "with" mechanism could fit
neatly into the same structure, by including a list of the types for
which values may be recorded, along with where on the stack the value is
stored. The efficiency would be much better than exception handling,
since no object would have to be copied (the recorded value lives in the
high-level function's stack frame). The only significant overhead is the
type comparison, which has to be able to handle derived types, but this
comparison can be made very efficient for non-class types.

Any comments are welcome.

--

Ciao,
Paul

(Please remove the "strip_these_words_" prefix from the return
address, which has been altered to foil junk mail senders.)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/05/11
Raw View
Paul D. DeRocco <strip_these_words_pderocco@ix.netcom.com> wrote:
:  enum overflowmode { wrap, clamp, trap };

:  int add_ints(int x, int y) {
:      int z = x + y;
:      if ((x > 0 && y > 0 && z < 0)
:              || (x < 0 && y < 0 && z > 0)) {
:          switch (default<overflowmode>(wrap)) {
:              case trap: throw overflow();
:              case clamp: z = z < 0 ? MAX_INT : MIN_INT;
:              }
:          }
:      return z;
:      }

:  with (clamp) a = add_ints(b, c);

How is it superior to a function template specialization?

template<overflowmode mode> int add_ints(int x, int y);
int add_ints<wrap >(int x, int y); // syntax ??
int add_ints<clamp>(int x, int y); // syntax ??
int add_ints<trap >(int x, int y); // syntax ??

a = add_ints<clamp>(b,c);

This approach also doesn't require you to change old code if you
add another overflowmode.

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: fjh@mundook.cs.mu.OZ.AU (Fergus Henderson)
Date: 1997/05/11
Raw View
"Paul D. DeRocco" <pderocco@ix.netcom.com> writes:

>Looking for comments...
>
>I've had an idea for passing "context" information into functions,
>bypassing the normal parameter-passing mechanism. It is roughly
>symmetric with exception handling, which passes information out of
>functions, bypassing the normal return mechanism. And in fact it would
>use some of the same internal mechanisms.
>
>Here is a suggested statement syntax:
>
> with ( expression ) statement
>
>This would record the type and value of the expression, and then execute
>the statement, during which the value of the expression may be
>interrogated by type using:
>
> default < type-id > ( expression )

I think it would be better if the default values were named,
rather than just being given a type.

This concept has been used before: it is called dynamic scoping.
It is easy to implement dynamic scoping in C++ as it is.

For example, instead of

> enum overflowmode { wrap, clamp, trap };
>
> int add_ints(int x, int y) {
>     int z = x + y;
>     if ((x > 0 && y > 0 && z < 0)
>             || (x < 0 && y < 0 && z > 0)) {
>         switch (default<overflowmode>(wrap)) {
>             case trap: throw overflow();
>             case clamp: z = z < 0 ? MAX_INT : MIN_INT;
>         }
>     }
>     return z;
> }

you can write

 enum Overflowmode { wrap, clamp, trap };
 DynamicallyScoped<Overflowmode> my_overflowmode;

 int add_ints(int x, int y) {
     int z = x + y;
     if ((x > 0 && y > 0 && z < 0)
             || (x < 0 && y < 0 && z > 0)) {
         switch (my_overflowmode) {
             case trap: throw Overflow();
             case clamp: z = z < 0 ? MAX_INT : MIN_INT;
         }
     }
     return z;
 }

and instead of

> with (clamp) a = add_ints(b, c);

you can write

 With<Overflowmode>(my_overflowmode, clamp), a = add_ints(b, c);

or

 {
  With<Overflowmode> override(my_overflowmode, clamp);
  a = add_ints(b, c);
 }

Implementation details left as an exercise to the reader.

--
Fergus Henderson <fjh@cs.mu.oz.au>   |  "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh>   |  of excellence is a lethal habit"
PGP: finger fjh@128.250.37.3         |     -- the last words of T. S. Garp.
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: "Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com>
Date: 1997/05/12
Raw View
Fergus Henderson wrote:
>
> "Paul D. DeRocco" <pderocco@ix.netcom.com> writes:
>
> >I've had an idea for passing "context" information into functions,
> >bypassing the normal parameter-passing mechanism. It is roughly
> >symmetric with exception handling, which passes information out of
> >functions, bypassing the normal return mechanism. And in fact it would
> >use some of the same internal mechanisms.
> >
> >Here is a suggested statement syntax:
> >
> >       with ( expression ) statement
> >
> >This would record the type and value of the expression, and then execute
> >the statement, during which the value of the expression may be
> >interrogated by type using:
> >
> >       default < type-id > ( expression )
>
> I think it would be better if the default values were named,
> rather than just being given a type.

I'm imagining that the implementation of this would be similar to the
implementation (or at least one commonly used implementation) of
exception handling, where the stack walkback can interrogate each stack
frame to see if a particular type value is provided (in the case of the
"with" statement) or caught (in the case of exceptions). I see the
desirability of using names -- after all, I might merely wish to catalog
an int value for the benefit of a low-level function, while not claiming
exclusive use of the type "int". But the same is true of exceptions: I
might want to throw an int, but not claim exclusive use of the type
"int". With exceptions, it's customary to wrap the int (or whatever
datum) in a special exception class, which acts as the "name", and then
throw that. I think the same approach is reasonable for the "with"
statement.

> This concept has been used before: it is called dynamic scoping.
> It is easy to implement dynamic scoping in C++ as it is.
>
> you can write
>
>         enum Overflowmode { wrap, clamp, trap };
>         DynamicallyScoped<Overflowmode> my_overflowmode;
>
>         int add_ints(int x, int y) {
>             int z = x + y;
>             if ((x > 0 && y > 0 && z < 0)
>                     || (x < 0 && y < 0 && z > 0)) {
>                 switch (my_overflowmode) {
>                     case trap: throw Overflow();
>                     case clamp: z = z < 0 ? MAX_INT : MIN_INT;
>                 }
>             }
>             return z;
>         }
>
> you can write
>
>         With<Overflowmode>(my_overflowmode, clamp), a = add_ints(b, c);
>
> Implementation details left as an exercise to the reader.

I take it that With<T>::With(x,y) records the state of x and sets it to
y, and then With<T>::~With() restores the state, right? I can also
imagine another implementation that avoids copying the value:

 template <class T> class With {
     T value;
     With<T>* prev;
     static With<T> latest = 0;
 public:
     With(T v): value(v), prev(latest) {latest = this;}
     ~With() { latest = prev; }
     static T& get(T& default) {
         if (latest) return latest->value;
         else return default;
         }
     };

 int add_ints(int x, int y) {
     ...
     switch (With<Overflowmode>::get(wrap)) ...
     ...
     }

 With<Overflowmode>(clamp), a = add_ints(b, c);

(I'm writing off the top of my head, so there may be errors.) The method
here is that each With<T> contains a pointer to the previous With<T> on
the stack, and the static "latest" contains a pointer to the current
one.

One problem with these is that you get a different template for every
type, where I'd like to be able to catalog a value of some derived type
in a high-level function, and then look up the value as a base type in a
low-level function.

But the main trouble with both techniques is that they only work in a
single-thread environment, yours because the value is global, mine
because the pointer to the latest value is global. That's it would have
to be built into the language. You could do this in Microsoft's
compiler, using the thread-local-storage modifier, but that's
proprietary.

--

Ciao,
Paul

(Please remove the "strip_these_words_" prefix from the return
address, which has been altered to foil junk mail senders.)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: "Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com>
Date: 1997/05/12
Raw View
Oleg Zabluda wrote:
>
> Paul D. DeRocco <strip_these_words_pderocco@ix.netcom.com> wrote:
> :       enum overflowmode { wrap, clamp, trap };
>
> :       int add_ints(int x, int y) {
> :           int z = x + y;
> :           if ((x > 0 && y > 0 && z < 0)
> :                   || (x < 0 && y < 0 && z > 0)) {
> :               switch (default<overflowmode>(wrap)) {
> :                   case trap: throw overflow();
> :                   case clamp: z = z < 0 ? MAX_INT : MIN_INT;
> :                   }
> :               }
> :           return z;
> :           }
>
> :       with (clamp) a = add_ints(b, c);
>
> How is it superior to a function template specialization?
>
> template<overflowmode mode> int add_ints(int x, int y);
> int add_ints<wrap >(int x, int y); // syntax ??
> int add_ints<clamp>(int x, int y); // syntax ??
> int add_ints<trap >(int x, int y); // syntax ??
>
> a = add_ints<clamp>(b,c);
>
> This approach also doesn't require you to change old code if you
> add another overflowmode.

The point is that the same function (perhaps in a library, or built into
an OS) can get additional information, beyond what is supplied via its
arguments, from the "environment" of the thread of execution. What's
more, the choice of overflow mode (to refer to this specific example)
might be made in a higher level function than the call to add_ints. That
is, a high-level piece of code might use the "with" statement to say, "I
don't want to hear about overflows, just clamp everything," and then
whenever add_ints gets called, it will be able to find out that this
mode is part of the state of the calling environment.

--

Ciao,
Paul

(Please remove the "strip_these_words_" prefix from the return
address, which has been altered to foil junk mail senders.)
---
[ comp.std.c++ is moderated.  To submit articles: Try just posting with your
                newsreader.  If that fails, use mailto:std-c++@ncar.ucar.edu
  comp.std.c++ FAQ: http://reality.sgi.com/austern/std-c++/faq.html
  Moderation policy: http://reality.sgi.com/austern/std-c++/policy.html
  Comments? mailto:std-c++-request@ncar.ucar.edu
]





Author: damian@cs.monash.edu.au (Damian Conway)
Date: 1997/05/12
Raw View
"Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com> writes:

>I've had an idea for passing "context" information into functions,
>bypassing the normal parameter-passing mechanism.

 [snip]

>This would search (perhaps by walking the stack) for the most
>recently entered "with" statement whose expression is of the
>specified type (or more derived type), and returns the corresponding
>value, without evaluating the expression in parentheses. If no
>matching "with" statement could be found, the result would be the
>value of the expression in parentheses, converted to the specified
>type. In addition, the expression could be a throw-expression, in
>which case the lack of a matching "with" statement would throw the
>specified exception.

You can already do all that (with a nearly identical syntax) without
extending the language. Note that you can't overload the "default"
keyword" (and a good thing too! :-)  I used "with_current" and
"with_current_or_throw", which are probably clearer anyway...

damian

-------cut-----------cut-----------cut-----------cut-----------cut------

// START OF MAGIC

struct _with_popper { virtual void pop(void) = 0; };

template <class T>
struct _with_popper_type : public _with_popper
{
 virtual void pop(void) { _with_stack<T>::current.pop(); }
};

template <class T>
struct _with_stack
{
 static stack< T,vector<T> > current;
 static _with_popper_type<T> popper;
};

template <class T> stack< T,vector<T> > _with_stack<T>::current;
template <class T> _with_popper_type<T> _with_stack<T>::popper;

template <class T>
_with_popper& _with_push(const T& value)
{
 _with_stack<T>::current.push(value);
 return _with_stack<T>::popper;
}

class _with_scope
{
public:
 _with_scope(_with_popper& popper)
  : myPopper(popper), mySeen(false) {}
 ~_with_scope(void) { myPopper.pop(); }
 operator bool(void) { return !mySeen && (mySeen=true); }
private:
 _with_popper& myPopper;
 bool  mySeen;
};

#define  with(value) for (_with_scope _with = _with_push(value);_with;)

#define  with_current(T,def)    \
 (_with_stack<T>::current.empty()  \
  ? (def)     \
  : _with_stack<T>::current.top())

template <class T, class Ex>
T _with_throw(T* t,const Ex& ex) { throw (ex); return *t; }

#define  with_current_or_throw(T,exception)  \
 (_with_stack<T>::current.empty()  \
  ? _with_throw((T*)0,exception)  \
  : _with_stack<T>::current.top())

// END OF MAGIC


enum overflowmode { wrap, clamp, trap };

int add_ints(int x, int y)
{
    int z = x + y;
    if ((x > 0 && y > 0 && z < 0) || (x < 0 && y < 0 && z > 0))
    {
 switch (with_current(overflowmode,wrap))
 {
     case trap:  throw (overflow());
     case clamp: z = z < 0 ? INT_MAX : INT_MIN;
     case wrap:  break; // DO NOTHING
 }
    }
    return z;
}

main()
{
 int a;
 int b = INT_MAX;
 int c = INT_MAX;

 a = add_ints(b, c);
 cout << a << endl;

 with (clamp) a = add_ints(b, c);
 cout << a << endl;

 with (trap) a = add_ints(b, c);
 cout << a << endl;
}
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: Oleg Zabluda <zabluda@math.psu.edu>
Date: 1997/05/14
Raw View
Paul D. DeRocco <strip_these_words_pderocco@ix.netcom.com> wrote:
: Oleg Zabluda wrote:
: >
: > How is it superior to a function template specialization?
: >
: > template<overflowmode mode> int add_ints(int x, int y);
: > int add_ints<wrap >(int x, int y); // syntax ??
: > int add_ints<clamp>(int x, int y); // syntax ??
: > int add_ints<trap >(int x, int y); // syntax ??
: >
: > a = add_ints<clamp>(b,c);
: >
: > This approach also doesn't require you to change old code if you
: > add another overflowmode.

: The point is that the same function (perhaps in a library, or built into
: an OS) can get additional information, beyond what is supplied via its
: arguments, from the "environment" of the thread of execution.

Well, if you don't want to make a decision at the call-time,
you can turn add_ints() into a functionoid, and use a (maybe
static, maybe not) variable to set the mode. You can set the
mode at any time, and I think it can be considered "additional
information, beyond what is supplied via its argument". You can
provide the info at the call-time as well, using the template
solution above.

Now, I've read other proposals, but so far I fail to see
a reason for such complex solutions, with such non-classic-C++
call syntax.

: What's
: more, the choice of overflow mode (to refer to this specific example)
: might be made in a higher level function than the call to add_ints. That
: is, a high-level piece of code might use the "with" statement to say, "I
: don't want to hear about overflows, just clamp everything," and then
: whenever add_ints gets called, it will be able to find out that this
: mode is part of the state of the calling environment.

I think the functionoid solution covers this as well. You can provide
mask-style interface to query/set mask and add/remove mask bits.
Or additionally implement stack-style change/restore interface. Whatever
you feel needed.

Oleg.
--
Life is a sexually transmitted, 100% lethal disease.
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]





Author: "Paul D. DeRocco" <strip_these_words_pderocco@ix.netcom.com>
Date: 1997/05/14
Raw View
Fergus Henderson wrote:
>
> It is a pity that C and C++ don't have decent support for multi-threading.
> But I don't think that is a good argument for adding dynamically scoped
> variables (or something similar) to the language.

I've often found that I wanted to use a global variable in order to
establish some sort of state information that can be set by high-level
functions and checked by low-level functions, but I couldn't because I
_really_ needed a thread-specific variable. I think the "with" statement
is perhaps a cleaner way of expressing the concept, and more obvious to
implement, than a global variable that is somehow declared thread-local.
The latter begs the question, local to which threads? while the former
answers the question, whichever threads you do the "with" statement in.

--

Ciao,
Paul

(Please remove the "strip_these_words_" prefix from the return
address, which has been altered to foil junk mail senders.)
---
[ comp.std.c++ is moderated.  To submit articles: try just posting with      ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu         ]
[ FAQ:      http://reality.sgi.com/employees/austern_mti/std-c++/faq.html    ]
[ Policy:   http://reality.sgi.com/employees/austern_mti/std-c++/policy.html ]
[ Comments? mailto:std-c++-request@ncar.ucar.edu                             ]