E.Ireland@massey.ac.nz (Evan Ireland) (04/30/91)
>It seems to me that the simplest and most "Scheme-like" approach is to add > (open-input-file filename [handler]) > (open-output-file filename [handler]) > -- a system-dependent code indicating the nature of the problem > -- the filename after (system-dependent) expansion >and if the handler returns normally its value would become the value of >the call to open-input-stream. The handler not being supplied, the >default would be to signal an error. Someone who -wants- #f to be >returned has only to do > (open-input-file filename (lambda (e n) #f)) I support this proposal. Let me summarise the good points as I see them. (1) No changes are required to existing code. (Chris Hanson points out that "a CL-like condition system can be added without changing any code, and is totally compatible with the standard.", but this approach is simpler). (2) Novice users, and expert users who *want* their programs to terminate with error conditions for these cases, don't have to provide "success" continuations. (Of course a condition system would allow this too, if you simply fail to handle the condition). (3) The idea of optional failure continuations for input/output procedures is "easier to understand" than a condition system. >I prefer the current situation of being able to say that a certain >procedure accepts arguments of a certain sort and signals an error if >the arguments are not valid. As Chris said, we can build a condition >system consistant with this. You are then free to handle the >conditions as you see fit (e.g. return some special error object). If you are programming in a mainly functional style, you will probably (1) Functions, such as sqrt, which are often partial. It is often tidier to check, before applying a function, whether the arguments you intend to pass are valid, and the tools for doing this are normal function applications e.g. (if (< x 0) (do-something-with-negative-number) (* 5 (sqrt x)) (2) Side-effecting procedures, mostly for I/O. Here the validity of an argument often depends on factors external to the program, e.g. the current state of the file system, which cannot be checked by the program, unless it resorts to other I/O procedures! (if (file-exists? filename) (open...) (do-something-about-error)) In this case checking beforehand is not feasible, because the state of the file system may change between the file-exists? and open... calls. Also the file-exists? call may fail! >At this juncture, seems to me, we could make the following change to the >language: for each builtin function, define another version of it with a >funny-looking name ("%CAR" or whatever) that takes explicit continuation >arguments for each of its various failure cases. (if (null? x) (do-somethinhg-with-empty-list) (do-something-with (car x))) For case (2), however, since we cannot check in this way, we need an --- Error values: these require changes to existing code (where the user *wants* termination with an error condition), and if error values are (mistakenly) not checked for, they will simply cause problems later on, possibly making some problems more difficult to trace. --- Condition systems: would solve the problem, but are more cumbersome than simple failure continuations. Also the error handlers are further removed from potential-error-causing procedure calls, making things more difficult to follow. --- Failure continuations: could be added as optional parameters to I/O procedures. Easy to understand, use, and implement, and requiring minimal changes to the language. Note that these are "plain" old continuations, not requiring call/cc. Anyway my view is biased since I am working on I/O systems for purely functional languages, where a continuation-passing style is adopted for I/O. In my scheme, every I/O operation has an error continuation, but a simple infix operator <> can be used to get the default "error" continuation, e.g. greet str = put str "Please enter your name: " <> get str <> \ name -> put str ("Hello " ++ name ++ ".\n") <> ... with (a <> b) defined as (a error b); error takes a string as argument and terminates execution. Now if I want to add error handling, I greet str = put str "Please enter your name: " (\ s -> handle_put_error s) $ get str (\ s -> handle_get_error s) $ \ name -> put str (\ s -> handle_put2_error s) ("Hello " ++ name ++ ".\n") <> ... -- Note: f $ a = f a ($ is useful for avoiding parentheses) (This is Haskell syntax). Now in Scheme, a full continuation-passing style may not be appropriate for the standard I/O, but optional failure continuations provide a similar effect to the <> operator above, with minimal changes required to the language standard. As I have noted, error handling for "functions" might be more appropriately handled by explicit checking. _______________________________________________________________________________ E.Ireland@massey.ac.nz Evan Ireland, School of Information Sciences, +64 63 69099 x8541 Massey University, Palmerston North, New Zealand.
markf@zurich.ai.mit.edu (Mark Friedman) (05/01/91)
In article <559sis-b@massey.ac.nz> E.Ireland@massey.ac.nz (Evan Ireland) writes: >It seems to me that the simplest and most "Scheme-like" approach is to add > (open-input-file filename [handler]) > (open-output-file filename [handler]) > -- a system-dependent code indicating the nature of the problem > -- the filename after (system-dependent) expansion >and if the handler returns normally its value would become the value of >the call to open-input-stream. The handler not being supplied, the >default would be to signal an error. Someone who -wants- #f to be >returned has only to do > (open-input-file filename (lambda (e n) #f)) I support this proposal. Let me summarise the good points as I see them. (1) No changes are required to existing code. (Chris Hanson points out that "a CL-like condition system can be added without changing any code, and is totally compatible with the standard.", but this approach is simpler). Passing error continuations and signalling errors is simpler than just signalling errors? (2) Novice users, and expert users who *want* their programs to terminate with error conditions for these cases, don't have to provide "success" continuations. (Of course a condition system would allow this too, if you simply fail to handle the condition). (3) The idea of optional failure continuations for input/output procedures is "easier to understand" than a condition system. We both agree that if you don't want to handle the error, both approaches are equivalent. So the question is whether (open-input-file file (lambda (e n) (my-handler e n))) is any simpler than (bind-handler file-open-error my-handler (lambda () (open-input-file file))) In this, the simplest case, I don't really think that there is any significant difference. In the most general case you have to have something like the following for the continuation approach: (define (my-procedure arg file-input-handler read-error-handler bad-input-handler) (read-and-process (open-input-file (get-file-name arg) file-input-handler) read-error-handler bad-input-handler)) with the list of handlers being passed around getting longer and longer as your programs get larger and larger. Dynamic error handlers neatly solve that problem. >I prefer the current situation of being able to say that a certain >procedure accepts arguments of a certain sort and signals an error if >the arguments are not valid. As Chris said, we can build a condition >system consistant with this. You are then free to handle the >conditions as you see fit (e.g. return some special error object). If you are programming in a mainly functional style, you will probably (1) Functions, such as sqrt, which are often partial. It is often tidier to check, before applying a function, whether the arguments you intend to pass are valid, and the tools for doing this are normal function applications e.g. (if (< x 0) (do-something-with-negative-number) (* 5 (sqrt x)) The question that we are trying to answer is what to do when you don't (or can't) check whether the arguments are valid. (2) Side-effecting procedures, mostly for I/O. Here the validity of an argument often depends on factors external to the program, e.g. the current state of the file system, which cannot be checked by the program, unless it resorts to other I/O procedures! For case (2), however, since we cannot check in this way, we need an --- Condition systems: would solve the problem, but are more cumbersome than simple failure continuations. I still don't see why they are more cumbersome (I argue that they are less cumbersome). Also the error handlers are further removed from potential-error-causing procedure calls, making things more difficult to follow. They are less difficult to follow than procedure calls with extraneous arguments. In the case where the handler is not bound immediately before a call to a procedure which might signal the condition corresponding to that handler, your approach would have to pass the handler along the call chain ala MY-PROCEDURE above. --- Failure continuations: could be added as optional parameters to I/O procedures. Easy to understand, use, and implement, and requiring minimal changes to the language. Well, I don't agree that they are easier to understand and use. Note that these are "plain" old continuations, not requiring call/cc. Actually, they look like "plain" old procedures to me. -Mark -- Mark Friedman MIT Artificial Intelligence Lab 545 Technology Sq. Cambridge, Ma. 02139 markf@zurich.ai.mit.edu