[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Portability (long)




	 4) RESET and FATAL-ERROR: I would propose a different, more powerful,
	 procedure instead, on top of which both of these could easily be
	 built.

	 (ABORT-TO-NEAREST-REPL <thunk>)
	...

    How is the "nearest repl" known to the runtime system?  How does a
    Scheme system recognize that a function is a (possibly user written)
    repl?  I don't have a problem with implementing RESET or FATAL-ERROR
    this way, but why standardize this function as a library routine?

The idea is that the runtime system provides facilities for building
repls which interact with these abort procedures (there are many in
MIT Scheme), and can provide additional functionality.  While this
procedure is useful for writing things like RESET, it is useful in its
own right: interrupt characters cause invocations of such procedures
in MIT Scheme.  Dan Friedman needs something like
ABORT-TO-NEAREST-REPL for some of the stuff he does as well.

	 I have strong objections against having a procedure named FATAL-ERROR
	 (or anything else with ERROR in it's name) which does not allow the
	 option of fixing the bug and proceeding.

    Perhaps I'm confused.  You don't want a FATAL ERROR to abort a
    computation?  This might be a valuable behavior in a (possibly
    parallel) multi-tasking search situation.  What would be the
    difference between FATAL-ERROR and ERROR ?  The thing that I am trying
    to clean up here is the confusion over CERROR vs ERROR as to when an
    error is fatal and when recoverable.  `ERROR' is pretty common and
    typing RECOVERABLE-ERROR gets a bit tedious. 

I don't think such things as non-recoverable errors exist, thus I
would not like to have anything like FATAL-ERROR which by definition
implies non-recovery.

	 I'm not too attached to the name ABORT-TO-NEAREST-REPL.  The similar
	 MIT Scheme procedure is called ABORT->NEAREST.

    I don't see this as a common usage in other Scheme implementations.
    Anyone? 

I don't know what Dan Friedman is using, but, again, he's told me that
he would like to see it in some standard.

**********

	 5) ERROR must be a special form in order to get the environment of
	 the "call".  In MIT Scheme, the ERROR special form expands into
	 something like
	 (ERROR-PROCEDURE (the-environment) <message> . <irritants>)

    ERROR works just fine as a procedure in many systems (Chez Scheme, T,
    and MacScheme).  The fact that MIT Scheme has a non-standard way to
    access the environment makes it convenient for ERROR to be a special
    form in MIT Scheme.  I would really prefer that you propose an
    standard environment mechanism if you want to insist that ERROR must
    be a special form (or better yet, a standard error handling
    mechanism).  I really prefer procedures to special forms. 

I'm very confused.  As far as I know, all correct scheme
implementations must be properly tail recursive, and this is what forces
error to be a special form.  For example, in

(define (foo x y)
  (if (valid? x)
      (error "foo: Invalid x" x)
      (operate-on x y)))

if ERROR is an ordinary procedure, FOO tail recurses into it, which
means taht a debugger has no way of finding out what Y was.
Furthermore, a debugger can't even find out that error was called from FOO!
Either ERROR is mostly useless, or calls to ERROR must be treated
specially by the compiler, making these calls special forms by definition.

I only wrote what MIT Scheme does as an example of a possible
expansion for error.  Another possibility is to have

(error . args)

expand into

((error-procedure . args))

which guarantees that ERROR-PROCEDURE is not invoked in tail recursive
position.

The problem is not first class environments, but tail recursion!

    How much code would break if I change the wording to allow ERROR to be a
    procedure *or* a special form?

Yuck!

**********

	 6) TRACE and UNTRACE: Do you want identifiers or procedure objects?  

	 If you want procedure objects you are forcing implementations to make
	 them modifiable.

	 If you want identifiers you must make TRACE and UNTRACE special forms
	 so they can get the environment of the "call" and therefore alter the
	 correct bindings.

    These are procedures in MacScheme and PCScheme and special forms in
    Chez Scheme and T.  PCScheme, T, and MacScheme all require procedure
    objects rather than procedure names.  I will change the wording to
    indicate this and allow TRACE and UNTRACE to be either procedures or
    special forms.

As an aside, they are procedures in MIT Scheme, requiring procedures
as arguments.

The reason why TRACE and UNTRACE must be special forms if they require
identifiers has nothing to do with first class environments but
with tail recursion.  It is similar to the case of ERROR:

(let ((foo (lambda () <some code>)))
  (trace 'foo))

if TRACE is an ordinary procedure, it cannot see the binding of foo
because of lexical scoping, and cannot parse the stack to find it due
to tail recursion, which has forced the LET frame to be reclaimed.

You may reply that TRACE applies only to "top level bindings", but
such beasts do not exist in MIT Scheme or in T.

**********

	 7) ALIAS: Why can't you just use (define <alias> <original>) ?

    This is hard to do with special forms.

    ...

    The reason for these bits of syntax is that we need to solve the
    portability problems now and we do not yet have syntactic extensions.
    Unless someone in Indiana moves fast, we will not have syntactic
    extensions in R4RS.  When we get syntactic extensions, these are easy
    to `define'.  I strongly suspect that the number of Scheme
    implementations is going to double every year or two for a while.  We
    need to be heading off the problems that will exist in 5 years unless
    fixed now. 

Although I agree with the gist of your argument, I don't see how this
applies to ALIAS.  Either you have a special form in the first place
which you are providing an alias for, or you do not.  If you have it,
you can use the original.  If you don't, ALIAS won't solve your
problem: In order for ALIAS to be of any help in portability, you need
conditional compilation or conditional loading according to
implementation.  Whatever you use to discriminate according to
implementation, you can use to provide definitions of the "missing"
special forms using each implementation's syntactic extension
facilities.  As far as I know, all implementations have ways of
providing syntactic extensions.

**********

	 13) READ-BYTE and WRITE-BYTE: The 0..255 range seems random.

    So you want 3..258?  I think that silent masking of overflow would be
    pretty random. 

I meant that 8 bit bytes are pretty arbitrary.  It would be nice to be
able to read in (for example) 16 bit quantities, 32 bit quantities,
etc.  Furthermore, although rare, there are still machines around that
are not based on 8 bit bytes, and they may become popular again in the
future.  I would hate to see current hardware praxis getting into
the language.

**********

	 14) BOUND?: must be a special form, since it has no way of obtaining
	 the environment of the "call".

    I will update to be either procedure or special form.

The problem is similar to that of ERROR and TRACE.  Consider, for example

(let ((x 3))
  (if (bound? 'X)
      #t
      #f))

If BOUND? is an ordinary procedure, it has no access to the
environment of the call, which may not even exist by the time BOUND?
is entered.

Declaring that BOUND? acts only on "top level" identifiers does not
solve the problem either.

**********

	 15) DEFINE-IF-<mumble>: These make sense only at top level of a file,
	 but not in internal definition position, and that makes them appear
	 strange.  I don't think that special forms like DEFINE-IF-<mumble> is
	 the way to conditionalize code, but this is a matter of taste.

    I don't see how these differ from non-conditional defines, which also
    appear strange.  I would be very happy to see a better proposal on
    conditional compilation.

The problem is clearest with DEFINE-IF-UNBOUND

(define (foo x)
  (define-if-unbound bar (+ x 3))
  (list x bar))

The contour where BAR is to be found depends on whether BAR is
available in the load-time environment, which is not visible at
compile time.  The code that the compiler must issue is not always
obvious, and it is always complicated.  Ordinary internal definitions
do not have such problems.  Would you consider LETREC-IF-UNBOUND?

Top level definitions do not present this problem if the compiler
treats references to such variables the same way that it treats
references to "globally" free variables, and are therefore handled by
the linker.

DEFINE-IF-IMPLEMENTATION has a similar problem in the presence of
separate compilation.

In general, as appealing as DEFINE-IF-<mumble> appears, I don't think
it is the way to achieve portability.

The way we tend to do that sort of thing around here is as follows:

Each program has two additional files, a compilation file, and a
loading file (they are often merged into one, but conceptually there
are two).  These files contain ordinary code which test the
implementation, query the user, etc, and then conditionally compile or
load the appropriate files.

For example, consider a hypothetical system consisting of three common
files (file1, file2, and file3), and for each implementation there is
an additional file with the implementation dependencies (including
syntactic extensions).  The compilation file would contain something like

(case (car (implementation-information))
  (("Implementation1")
   (compile-file "implementation1")
   (load-file "implementation1")) ; To provide syntactic extensions
  (("Implementation2")
   (compile-file "implementation2")
   (load-file "implementation2"))
  ...
  (else
   (error "Unknown implementation -- please port")))

(compile-file "file1")
(compile-file "file2")
(compile-file "file3")

The loading file would have similar code which would load the
appropriate parts of the system.

Although this mechanism is cumbersome for small programs, it works
very well for relatively large systems, and avoids rats' nests of
unreadable conditionally compiled code.

**********

	 17) AUTOLOAD: Must be a special form like DEFINE and DEFINE-IF-<mumble>.

    PCScheme has an AUTOLOAD-FROM-FILE procedure.  I will have the definition
    allow either a procedure or a special form.

Same problem as TRACE, BOUND?, etc.  I assume that in PCScheme it
always loads into the "global" environment.