Re: Exceptions are too return values!
On 15-Jun-1998, Peter White peter@galois wrote: On June 15, Fergus Henderson writes As noted earlier, things like heap overflow, and stack overflow are different from other kinds of exceptions. They can't be modelled using the domain-theoretic semantics. Rather, they reflect the failure of the operational semantics to accurately reflect the domain-theoretic semantics. Thus the treatment of these exceptions may need to be different to the treatment of ordinary exceptions. One of the advertisements of Haskell is that you can reason about your program, by performing mathematical proofs about the program. Haskell has gone a long way to incorporating IO and stateful computations in such a way that you still get referential transparency, and you can still reason about programs. If the operational semantics fails to reflect, the domain-theoretic semantics, then it would appear that the ability to reason about the programs dissappears. This is not the case here. The reason is that although the operational semantics are not complete w.r.t. the denotational (domain-theoretic) semantics, they are sound. That is, you can't use the denotational semantics to prove that your program won't get a heap overflow; but you can use them to prove that if your program doesn't get a resource failure like that, then it will compute the right answer. If you want to reason about resource limits, then you need to use an operational semantics, not the denotational semantics. I think it is a requirement upon a Haskell implementation to preserve the independence of threads by "localizing" the resources to the threads, such that each thread can predict by itself, independently of any other thread, whether its resources will be sufficient. I don't think this is desirable in the general case. I think it would be useful to *allow* threads to reserve resources, but often it is difficult to predict in advance exactly how much each thread will use, and frequently it is better to deal with resource failures when they arise. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: FW: Exceptions are too return values!
I thought about this problem some more, and I have realized that the problem of nondeterminacy for Haskell exceptions would in fact be considerably worse that I had previously considered. The trouble is that in the general case the problem is not just that the choice of which exception is raised is nondeterministic -- instead, it would be much worse: the choice of whether you raise an exception or loop forever can be also be nondeterministic. This occurs because of expressions such as `0/0 + loop'. Or, to take a more realistic (and nasty) example, `f 0' where `f x = 1/x + g x' where `g x' happens to loop if `x' is zero. I don't agree that this is a problem. If (g x) loops when x is zero then you should jolly well test for that: f x | x == 0= raise "x is zero" | otherwise = 1/x + g x I simply don't think it's reasonable to comletely prescribe the evaluation order of a lazy functional program. At the moment, Haskell has the fiction that a divide-by-zero exception and non-termination are the same value, i.e. bottom. That allows us to say that the behaviour of f x = 1/x + g x is identical regardless of whether "+" evaluates its first argument first or second. But we all know that the behaviour in these two cases is quite different: one prints a message and halts, and the other fails to terminate. So in this sense the behaviour of Haskell programs is already non-deterministic. The nice thing about the NDSet story is that it makes clear precisely where the non-determinism occurs. Equational reasoning is not impaired, nor is the implementation penalised. I think it's a great idea. So I appear to be in disagreement here with Alex, Amr, and Fergus about the importance of being able to say precisely which exception is raised. I'm quite content with knowing which *set* of exceptions can be raised. Ha! Simon
RE: FW: Exceptions are too return values!
At 11:06 +0200 98/06/16, Erik Zuurbier wrote: ... Exceptions are merely a way to structure the code, so that the main line and error handling can be neatly separated. This is the original idea, but I pointed out that exceptions are in fact much deeper: They can be used as a programming technique too, and further, many common language constructs (such as "return", "break", etc in C++) can be viewed as special cases of exception handling. Hans Aberg * Email: Hans Aberg mailto:[EMAIL PROTECTED] * Home Page: http://www.matematik.su.se/~haberg/ * AMS member listing: http://www.ams.org/cml/
RE: FW: Exceptions are too return values!
On Tue, 16 Jun 1998, Erik Zuurbier wrote: I have read many, but not all of the messages on this subject. Did any of those shed any light on the intended use of exceptions? Maybe that could explain the disagreement. I can imagine: 1) You use exceptions for debugging your program, with the goal (naive maybe) that none will ever be raised in the final program. 2) You learn to rely on the defined behaviour, deterministic or not, and the final program can be perfectly acceptable if it raises any number of exceptions as long as they are caught and handled in time. Exceptions are merely a way to structure the code, so that the main line and error handling can be neatly separated. There's a third case, I think: 3) You are writing code which may be reused (either by design or fortuitous circumstance); consequently if any of your `externally usable' functions can throw an exception, you can't rely upon the user reading and understanding you code to the degree that they appreciate all the nuances of exactly which exception was thrown in some intricate circumstance. (I think haskell is still a way from the C++ situation where there are distributed _binary libraries_ which can throw exceptions, so you can't assume you can even read the source.) Thus you have to deliberately make circumstances where knowing _exactly_ what the primary error is (rather than just that `an error occurred whilst doing this overall thing') exactly predictable yourself, strictifying code if necessary. cheers, dave email: [EMAIL PROTECTED] "Taught the wife some html. __Bad www.cs.bris.ac.uk/~tweed/pi.htm move__." -- Alan Cox work tel: (0117) 954-5253
Re: Exceptions are too return values!
On 15-Jun-1998, Fergus Henderson [EMAIL PROTECTED] wrote: On 12-Jun-1998, Scott Turner [EMAIL PROTECTED] wrote: At 14:40 1998-06-10 +0100, you wrote: Here's a reasonable design for exceptions in Haskell: * handle :: (String - IO a) - IO a - IO a You probably realized more quickly than I how this can leak exceptions. ... Is this considered a drawback? This kind of exception handling can "leak" exceptions, but not in the way you described. ... What I mean is main = do quotient - handle (const (return 0)) (return (0 / 0) -- Looks plausible -- but the exception isn't raised yet. print quotient -- Here the expression 0/0 is evaluated -- and the exception is raised with no handler. This is not correct. This example would print out `0' rather than raising an uncaught division by zero exception. I'm afraid I must retract those statements. Scott Turner was quite correct, and I was mistaken. My apologies! As Scott pointed out to me in personal email, SLPJ's definition of `handle' | * handle :: (String - IO a) - IO a - IO a | (handle h a) tries to perform the action a. | If doing so delivers a set of exceptional values then | apply the exception handler h to the string that names | one of them. It is not defined which of the exceptional | values is picked. means that it only catches exceptional values in the I/O action, not exceptional values in the return value. Regarding Scott's question Is this considered a drawback? my answer is still much the same -- yes, it's a drawback, but I'd place the blame more on laziness than exception handling. I consider it only a minor drawback, since the "leakage" can be avoided if you use a version of `handle' which is strict in the return value, e.g. strict_handle handler action = handle handler strict_action where strict_action = do value - action seq value return value -- or with `hyper_seq' instead of `seq' -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: FW: Exceptions are too return values!
At 14:40 +0100 98/06/10, Simon L Peyton Jones wrote: Here's a reasonable design for exceptions in Haskell: A think one can use a monadic approach, as a monad (X, unit_X, bind_X): HaskellX - HaskellX where HaskellX is and extension of Haskell with exceptions. * A value of Haskell type T can be EITHER one of the values we know and love (bottom, or constructor, or function, depending on T), OR it can be a set of exceptional values. So this says that X(T) = T | Exception, where Exception is a type in HaskellX which labels objects in Haskell as exceptions. This monad is such as old Haskell code can always be run in the new HaskellX. * raise :: String - a (raise s) returns a single exceptional value, named by string s I would suggest that an exception could not just be a string, but any value in HaskellX. * All strict operations (case, +, etc) return the union of the exceptional values returned by their strict arguments For example, if both arguments to "+" return an exceptional value then "+" returns both. Similarly, any strict context. Actually, if a function f in Haskell has type T, and is altered so that it raises an exception, then its type becomes T | Exception. If a function g: A - B is altered to raise an exception, there is a difference between the types A - (B | Exception) and (A | Exception - B | Exception), but the monad is such that one can always simplify to the latter, and one can use the abbreviation (A - B) | Exception for the latter. So the type handling mechanism should not need to be that much more complicated: Just replace T with T | Exception if the function raises an exception. Functions that do not raise an exception can always be extended to this, T | Exception, on the fly when encountering an exception via the monad proeprties, so these functions need not get an altered type. * handle :: (String - IO a) - IO a - IO a (handle h a) tries to perform the action a. If doing so delivers a set of exceptional values then apply the exception handler h to the string that names one of them. It is not defined which of the exceptional values is picked. Then handle() should not only handle strings and IO, but any Exception (of course), and any action a. The handle function must be able to determine if it can handle the exception, so it should have a function f: Exception - Boolean as an extra argument; the exception is handled only if this evaluates to "true" for the exception it handles. Hans Aberg * Email: Hans Aberg mailto:[EMAIL PROTECTED] * Home Page: http://www.matematik.su.se/~haberg/ * AMS member listing: http://www.ams.org/cml/
Re: FW: Exceptions are too return values!
On 16-Jun-1998, Simon L Peyton Jones [EMAIL PROTECTED] wrote: [Fergus wrote:] I thought about this problem some more, and I have realized that the problem of nondeterminacy for Haskell exceptions would in fact be considerably worse that I had previously considered. The trouble is that in the general case the problem is not just that the choice of which exception is raised is nondeterministic -- instead, it would be much worse: the choice of whether you raise an exception or loop forever can be also be nondeterministic. This occurs because of expressions such as `0/0 + loop'. Or, to take a more realistic (and nasty) example, `f 0' where `f x = 1/x + g x' where `g x' happens to loop if `x' is zero. I don't agree that this is a problem. If (g x) loops when x is zero then you should jolly well test for that: f x | x == 0= raise "x is zero" | otherwise = 1/x + g x Simon, I'm sure that a really thorough programmer such as yourself would never forget to insert such a test. But, as was recently demonstrated on this mailing list ;-), I'm quite fallible. I'm sure there are many other fallible Haskell programmers around. To minimize the bugs in my programs, I use a lot of different tools and techniques. I write in languages that have a lot of static checking, so that the compiler will catch a lot of my mistakes. I get my colleagues to review my code. And last but not least, I test my code. For the kind of bug referred to above, static checking isn't going to help (at least not given the current state of the art -- no doubt improvements are possible). Code reviews would help, but my colleagues are fallible too. And this kind of bug is very difficult to test for. Not only is it difficult to construct test cases that exercise all the exceptional cases, even that is not sufficient, since it might work fine with one implementation and then fail with another. So, I don't think it is reasonable to say that this is not a problem. It may not be a big problem, but I do consider it a problem. Now, we can certainly debate the likely frequency of such bugs, and their cost, and compare this with the advantages and disadvantages of exception handling. In fact, it does seem likely that such bugs would be very rare. The cost of each such bug may be high, but if they occur infrequently enough, then the overall cost will be small. So maybe you just meant that it wasn't likely to be a significant problem in practice. If that was what you meant, then I'm inclined to agree with you. I simply don't think it's reasonable to comletely prescribe the evaluation order of a lazy functional program. Why not? Because it would inhibit optimization? This is true, but for some applications reliability (and hence determinism) is much more important than efficiency. For these applications, I think it would be reasonable to specify the behaviour exactly, even if it means giving up some optimization opportunities. Do you agree? Conversely, there are many applications for which efficiency is more important than determinacy, so for those applications I agree the behaviour should not be specified exactly. Fortunately a single language can support both kinds of applications, as I outlined in previous mail. At the moment, Haskell has the fiction that a divide-by-zero exception and non-termination are the same value, i.e. bottom. That allows us to say that the behaviour of f x = 1/x + g x is identical regardless of whether "+" evaluates its first argument first or second. But we all know that the behaviour in these two cases is quite different: one prints a message and halts, and the other fails to terminate. So in this sense the behaviour of Haskell programs is already non-deterministic. That's true, but since both the fatal error message and non-termination constitute program bugs, this is not so much of a worry. The nondeterminism doesn't make testing any more difficult, for example. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
RE: FW: Exceptions are too return values!
SLPJ writes: So I appear to be in disagreement here with Alex, Amr, and Fergus about the importance of being able to say precisely which exception is raised. I'm quite content with knowing which *set* of exceptions can be raised. I have read many, but not all of the messages on this subject. Did any of those shed any light on the intended use of exceptions? Maybe that could explain the disagreement. I can imagine: 1) You use exceptions for debugging your program, with the goal (naive maybe) that none will ever be raised in the final program. 2) You learn to rely on the defined behaviour, deterministic or not, and the final program can be perfectly acceptable if it raises any number of exceptions as long as they are caught and handled in time. Exceptions are merely a way to structure the code, so that the main line and error handling can be neatly separated. Erik Zuurbier
Re: Exceptions are too return values!
On 12-Jun-1998, Scott Turner [EMAIL PROTECTED] wrote: At 14:40 1998-06-10 +0100, you wrote: Here's a reasonable design for exceptions in Haskell: * handle :: (String - IO a) - IO a - IO a You probably realized more quickly than I how this can leak exceptions. ... Is this considered a drawback? This kind of exception handling can "leak" exceptions, but not in the way you described. Yes, this is a drawback, but it's not nearly as big a drawback it would be if exceptions could leak in the way you were talking about. Furthermore, the leakage seems to be inherent to lazy evaluation, so I'd consider it a drawback of lazy evaluation rather than a drawback of exception handling. The user can avoid such leakage, so long as they're willing to lose some laziness. Details below. What I mean is main = do quotient - handle (const (return 0)) (return (0 / 0) -- Looks plausible -- but the exception isn't raised yet. print quotient -- Here the expression 0/0 is evaluated -- and the exception is raised with no handler. This is not correct. This example would print out `0' rather than raising an uncaught division by zero exception. The reason is basically that the handler is established lazily too. When `print' evaluates its argument, first the handler is established, then 0/0 is evaluated, then the handler catches the exception and returns 0. This may not have been obvious from SLPJ's original description, but if your consider the domain-theoretic semantics, it has to be this way. SLPJ's original description was as follows: | * handle :: (String - IO a) - IO a - IO a | (handle h a) tries to perform the action a. | If doing so delivers a set of exceptional values then | apply the exception handler h to the string that names | one of them. It is not defined which of the exceptional | values is picked. The result of performing the action `return (0 / 0)' is a (singleton) set of exceptional values, so the effect of `handle (const (return 0)) (return (0 / 0))' must be to apply `const (return 0)' to one of those values, which in turn has the same effect as `return 0'. The fact that this is all evaluated lazily doesn't change the semantics. If you want to understand the operational semantics in more detail, then it may perhaps be clearer if you look at my implementation of his `handle' using `ndset_handle' and `ndset_choose', since that breaks things up into smaller pieces, seperating out the exception handling from the nondeterministic choice. But probably the simplest way of seeing it is to look at the domain-theoretic semantics as outlined above. So, your example is not a problem. However, it is true that this kind of exception handling does in a certain sense "leak" exceptions. This is because `handle' only catches exceptions that occur during the evaluation of the top level of the value, it doesn't catch exceptions that occur duing evaluation of the sub-components. For instance, if we just modify your example slightly, then we get an example where exceptions really do "leak" out: main = do list - handle (const (return [])) (return [0 / 0]) print list This example will print "[" and then throw an uncaught division by zero exception. In order to avoid this, the user needs to force strict main = do list - handle (const (return [])) (return e) `hyperseq` e where e = return [0 / 0] print list Here `hyperseq' is a function that is like `seq' except that it forces complete evaluation, not just evaluation to WHNF (weak head normal form). class HyperEval a where hyperstrict :: (a - b) - a - b hyperseq :: a - b - b hyperstrict f x = x `hyperseq` f x instance HyperEval a = HyperEval [a] where [] `hyperseq` val = val (x:xs) `hyperseq` val = x `hyperseq` (xs `hyperseq` val) If we use a version of `handle' where the handler is the second argument rather than the first (a good idea, IMHO!), then the example could be written slighly more elegantly, using `hyperstrict' rather than `hyperseq', as either main = do list - hyperstrict handle (return [0/0]) (const (return [])) print list or if you prefer main = do list - (return [0/0]) `hyperstrict handle` (const (return [])) print list P.S. Is there any reason why something like `HyperEval' isn't built in to Haskell, or at least include in the Haskell Library report? Is there any implementation-specific precedent for something like this in say ghc? -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: Exceptions are too return values!
On 13-Jun-1998, Peter White peter@galois wrote: I wonder if there is another issue relating potential nondeterminism of exceptions to the independence of threads. It is supposed to be the case that two different threads have behavioral independence, so that an implementation could run the threads in any order, interleave their execution in any way, and the two threads would still give the same results. Well, that depends on whether you want parallelism, or concurrency. If you just want parallelism, i.e. you're just using threads to improve performance, then yes, the order of interleaving should not affect the results. But if you're using concurrency, then this isn't necessarily true -- the order of interleaving may affect the results, and this may be exactly what you want. Take the case of a heap overflow exception. As noted earlier, things like heap overflow, and stack overflow are different from other kinds of exceptions. They can't be modelled using the domain-theoretic semantics. Rather, they reflect the failure of the operational semantics to accurately reflect the domain-theoretic semantics. Thus the treatment of these exceptions may need to be different to the treatment of ordinary exceptions. In particular, instead of data MaybeException a = OK a | GotException (NDSet Exception) ndset_catch :: a - MaybeException a you need something like data ResourceFailure = StackOverFlow | HeapOverFlow | ... data MaybeResourceFailure a = Computed (MaybeException a) | Failed ResourceFailure ndset_catch_all :: a - NDSet MaybeFailure Timeouts may also be considered as resource failures. Interrupt handlers could also be considered as exceptions or resource failures, but I think is probably nicer to consider them as forms of concurrency. Suppose the two threads demand more memory than is provided in the computer. One of the two threads will hit a heap overflow exception. In order to have the implementation guarantee thread independence, the heap overflow of one thread cannot depend upon the memory consumption of the other thread. If there is a dependence, then one thread can determine the behaviour of the other thread by choosing to consume memory on the heap or not. If you're worried about covert communication channels, then yes, you have to worry about things like this. But generally covert communication channels are not an issue. So in general it's enough to say that whether or not you get a HeapOverflow resource failure is nondeterministic. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: Exceptions are too return values!
On Mon, 15 Jun 1998, Fergus Henderson wrote: P.S. Is there any reason why something like `HyperEval' isn't built in to Haskell, or at least include in the Haskell Library report? Is there any implementation-specific precedent for something like this in say ghc? Dave Tweed [EMAIL PROTECTED] added: I'd like to second this. It would have been very useful in some of the stuff I've written, particularly since (understandably enough) when using newtype you can't put ! annotations within the data-type. I believe this is what the derive program (available from Glasgow's web site) was originally developed for. -- Alastair Reid Yale Haskell Project Hacker [EMAIL PROTECTED] http://WWW.CS.Yale.EDU/homes/reid-alastair/
Re: FW: Exceptions are too return values!
On 12-Jun-1998, Alastair Reid [EMAIL PROTECTED] wrote: Fergus Henderson [EMAIL PROTECTED] points out that our exception handling scheme hits problems if you hit an infinite loop instead of an exception. Yes, this is a problem - and not a pretty one. Fixes: ... 3) Add timeouts (and ctrl-C handling) into the mix - practical approximations to solving the halting problem. Fergus then lists a bunch of options and says: Of these options, I'm afraid that (a), the status quo, is looking to me like the best of a bad lot, albeit with (c) (i) a close second. ... some of us have to write programs that keep working. For example, I'm busy hacking on our Robo-Haskell code at the moment - it just isn't acceptable for that kind of code to print an error message and halt. For programs like that, where reliability is very important, wouldn't it be better to use deterministic exceptions [i.e. (c)(i)], even if it means giving up some optimization? As far as I can see, that means we either have to eliminate pattern match failure, the error function, heap overflow, stack overflow and infinite loops or we have to add exception handling in some form. Well, eliminating pattern match failure would not be a bad idea. I think it's better to require programmers to put explicit calls to `error' if that's what they want. Regarding infinite loops, and the use of the `error' function, in the long term future I hope we see systems that have much better support for the use of formal methods, so that system and the program could between them provide proofs of termination and proofs that `error' is never called. Much work has already been done in this general area, including some by some of my colleagues [1], but there is still much to be done -- making this practical is still very much a research issue. Regarding resource exhaustion such as heap overflow, stack overflow, and (for hard real-time programs) timeouts, yes, you do need to provide a way of handling these, and these are going be have to be nondeterministic, at least from the program's point of view (that is, at the level of the denotational semantics rather than the operational semantics). But that doesn't necessarily mean that you should use the same approach for other kinds of exceptions -- as noted in other messages in this thread, resource failures are different to other kinds of exceptions. I suppose that overall, the disadvantages of nondeterminstic exceptions (compared to the status quo) for program portability and reliability is likely to be significantly outweighed by the advantage in expressiveness, and the consequently increased robustness that they provide. I was a bit shocked when first I realized that the nondeterminism could affect termination so easily, but on reflection I guess that in the big picture this is likely to be a rare event and so even nondeterministic exceptions are likely to be a significant win overall. However, for reliability and portability, if Haskell does end up adopting nondeterministic exceptions, I'd like to see a requirement that implementations offer an option which would inhibit any optimizations that might affect which exceptions were thrown. The interface would remain the same (using NDSet and/or the IO monad), so the effect of this option would just that the operational semantics would be more tightly specificied. [1]Termination analysis for Mercury. Chris Speirs, Zoltan Somogyi and Harald Sondergaard. Technical Report 97/9, Department of Computer Science, University of Melbourne, Melbourne, Australia, July 1997, 25 pages. Available via http://www.cs.mu.oz.au/mercury/papers.html. This paper presents the algorithms of the Mercury termination analyser, discusses how real-world aspects of the language such as modules, higher-order features, foreign language code, and declarative input/output can be handled, and evaluates the performance of the analyser both on a set of standard test programs and on the Mercury compiler itself. A shorter version of this paper was published in the Proceedings of the Fourth International Static Analysis Symposium, Paris, France, September 1997. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
[Fwd: Re: Exceptions are too return values!]
--3E1737327A Content-Type: text/plain; charset="us-ascii" Alastair Reid wrote: I believe this is what the derive program (available from Glasgow's web site) was originally developed for. Hi, I'm the implementor of the software formerly known as 'Derive`, and would just make a few points. 1. I've been threatened with legal action from Soft Warehouse Inc. for infringing their trademark "DERIVE". The full details are at http://www.dcs.gla.ac.uk/~nww/Derive/derivehome.html In summary I'm not allowed to refer to my software using that name, and must also inform others to do the same. 2. The software formerly known as 'Derive' was a little project written during the first year of my PhD. Although it originates from Glasgow, one should not assume it enjoys the same level of robustness or support as other Glasgow FP tools. 3. Personally, I think that there is a need for a type-sensitive preprocessor for Haskell. Extending the derivable classes was the motivating example, but there are other applications. The software formerly known as Derive is a first attempt at this. I think it is time for someone to develop this idea properly into a robust system. However, due to my PhD I don't have time for this. regards noel. -- Noel Winstanley Dept of Computing Science University of Glasgow http://www.dcs.gla.ac.uk/~nww/ mailto:[EMAIL PROTECTED] --3E1737327A Content-type: message/rfc822 Return-Path: [EMAIL PROTECTED] Delivery-Date: Mon, 15 Jun 1998 14:58:59 +0100 Received: from dcs.gla.ac.uk by vanuata.dcs.gla.ac.uk id [EMAIL PROTECTED]; Mon, 15 Jun 1998 14:57:05 +0100 Old-Received: from easter.dcs.gla.ac.uk.dcs.gla.ac.uk (actually host easter) by vanuata with SMTP DCS (MMTA) with ESMTP; Mon, 15 Jun 1998 14:56:57 +0100 Old-Received: by easter.dcs.gla.ac.uk.dcs.gla.ac.uk (8.8.5/Dumb)id OAA00405; Mon, 15 Jun 1998 14:56:55 +0100 Old-Received: from haggis.cs.yale.edu (actually host HAGGIS.AI.CS.YALE.EDU) by vanuata with SMTP (MMTA) with ESMTP; Mon, 15 Jun 1998 14:39:41 +0100 Old-Received: from haggis.cs.yale.edu (reid@localhost [127.0.0.1]) by haggis.cs.yale.edu (8.8.7/8.8.7) with ESMTP id JAA27815; Mon, 15 Jun 1998 09:37:53 -0400 Message-Id: [EMAIL PROTECTED] To: Dave Tweed [EMAIL PROTECTED] cc: [EMAIL PROTECTED] Subject: Re: Exceptions are too return values! In-reply-to: Your message of "Mon, 15 Jun 1998 10:03:18 BST." Pine.SGI.3.96.980615095746.2241B-10@neon Sender: [EMAIL PROTECTED] Precedence: bulk 8Qxd$QC/sdeK{93/{KA]T@gir{b8(rd5/zL85UcsTGty!z9Nx%Z+0e193YVEXFcWdM.]+uyVYA6 WNNn]tdh-oQ]/#\R;Vts^}W]a%+%VqSEAu Date: Mon, 15 Jun 1998 09:37:52 -0300 From: Alastair Reid [EMAIL PROTECTED] Resent-Date: Mon, 15 Jun 1998 14:57:05 +0100 Resent-From: [EMAIL PROTECTED] Resent-To: [EMAIL PROTECTED] MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" On Mon, 15 Jun 1998, Fergus Henderson wrote: P.S. Is there any reason why something like `HyperEval' isn't built in to Haskell, or at least include in the Haskell Library report? Is there any implementation-specific precedent for something like this in say ghc? Dave Tweed [EMAIL PROTECTED] added: I'd like to second this. It would have been very useful in some of the stuff I've written, particularly since (understandably enough) when using newtype you can't put ! annotations within the data-type. I believe this is what the derive program (available from Glasgow's web site) was originally developed for. -- Alastair Reid Yale Haskell Project Hacker [EMAIL PROTECTED] http://WWW.CS.Yale.EDU/homes/reid-alastair/ --3E1737327A--
Re: Exceptions are too return values!
Here is an input on the exception handling question: In (pseudo) C++, one can write try { ... } catch (A) { if (C) then handle_it else rethrow } But in a functional language it would be more reasonable to write if (C) then catch(A) { handle_it } or something like that, and let the compiler rewrite it to the C++ construction above (the latter which has the advantage that the handling points are known in advance). Then this could be generalized: If f contains the handling of exceptions E_1, ..., E_k, then f(x) rewrites to catch (E_1, ..., E_k) { f(x), rethrow if not caught } Then f(x) is only computed if needed because it handles the exception, but it also ensures that the exception is handled if f has the capacity to do so. I am not sure how this idea would work out in a functional language, but this would be a part of the analysis one would have to do when implementing exceptions. Hans Aberg * Email: Hans Aberg mailto:[EMAIL PROTECTED] * Home Page: http://www.matematik.su.se/~haberg/ * AMS member listing: http://www.ams.org/cml/
Re: FW: Exceptions are too return values!
On 11-Jun-1998, Amr A Sabry [EMAIL PROTECTED] wrote: There is one aspect of Java that is relevant here: A Java implementation is free to load and link classes in any order, strictly or lazily, but it MUST report exceptions as if it had loaded and resolved the classes lazily. I think Haskell should have the same restriction: it would bad to receive different exceptions because a Haskell implementation decided to evaluate an argument strictly before it is needed. Java got that right. --Amr Java chose to favour determinacy over efficiency. That's a reasonable decision, but it isn't the "right" decision for all applications. For some applications, efficiency is more important than determinacy. This applies to other areas of the Java spec too, such as floating point. floating point performance problems on some platforms such as DEC Alpha. The solution? Last time I looked, I think implementations of Java for those platforms simply didn't conform to the spec. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: Exceptions are too return values!
At 10:50 +1000 98/06/12, Fergus Henderson wrote: Infinities are probably best treated as a seperate issue. That is, infinities should not correspond to exceptions. If you have a type which supports infinities, then 1/0 should return infinity, not raise an exception. Conversely, if you want 1/0 to not raise an exception, then your type should support infinities. I think it is best to let 1/0 throw an exception "divide-by-zero" -- then this can be used to build types that support infinities (like projective spaces). Hans Aberg * Email: Hans Aberg mailto:[EMAIL PROTECTED] * Home Page: http://www.matematik.su.se/~haberg/ * AMS member listing: http://www.ams.org/cml/
Re: FW: Exceptions are too return values!
Fergus Henderson [EMAIL PROTECTED] points out that our exception handling scheme hits problems if you hit an infinite loop instead of an exception. Yes, this is a problem - and not a pretty one. Fixes: 1) Remove fixpoints so that infinite loops don't happen. Ok, so this isn't really an option - but it's worth mentioning because even this isn't enough! The problem is that our optimiser might rearrange code so that we run out of heap instead of hitting an error. 2) Solve the halting problem. See (1). 3) Add timeouts (and ctrl-C handling) into the mix - practical approximations to solving the halting problem. Actually, all we really need is Concurrent Haskell and the ability to kill threads. (Being able to suspend a thread would probably be useful too.) The new GHC-Hugs runtime system will have both. Fergus then lists a bunch of options and says: Of these options, I'm afraid that (a), the status quo, is looking to me like the best of a bad lot, albeit with (c) (i) a close second. You're welcome to do that and we certainly aren't going to try to add exception handling to any kind of standard before we have lots of experience of how well it works in practice. (Though it would be nice if all Haskell implementations happened to have a compatible "non-standard" exception handling mechanism :-) ) But some of us have to write programs that keep working. For example, I'm busy hacking on our Robo-Haskell code at the moment - it just isn't acceptable for that kind of code to print an error message and halt. I remain convinced that: Haskell will remain a toy language until it can be used to write robust programs. As far as I can see, that means we either have to eliminate pattern match failure, the error function, heap overflow, stack overflow and infinite loops or we have to add exception handling in some form. Alastair
Re: Exceptions are too return values!
At 14:40 1998-06-10 +0100, you wrote: Here's a reasonable design for exceptions in Haskell: * handle :: (String - IO a) - IO a - IO a You probably realized more quickly than I how this can leak exceptions. What I mean is main = do quotient - handle (const (return 0)) (return (0 / 0) -- Looks plausible -- but the exception isn't raised yet. print quotient -- Here the expression 0/0 is evaluated -- and the exception is raised with no handler. Is this considered a drawback? -- Scott Turner [EMAIL PROTECTED] http://www.ma.ultranet.com/~pkturner
Re: FW: Exceptions are too return values!
On 10-Jun-1998, S. Alexander Jacobson [EMAIL PROTECTED] wrote: This sounds like what I wanted. Just a few questions: * A value of Haskell type T can be EITHER one of the values we know and love (bottom, or constructor, or function, depending on T), OR it can be a set of exceptional values. * raise :: String - a (raise s) returns a single exceptional value, named by string s I presume that the programmer does not need to declare that a function may throw an exception ... Correct. that it is inferred from the "raise" in the function. The idea is that *any* function may potentially throw an exception. Furthermore, I assume that you can use the return value in another function without unwrapping it e.g. Also correct. * All strict operations (case, +, etc) return the union of the exceptional values returned by their strict arguments For example, if both arguments to "+" return an exceptional value then "+" returns both. Similarly, any strict context. It would make debugging easier if the exception picked was consistent accross implementations. It doesn't matter which one, but it does matter that it is the same. (maybe you require that Exceptions implement Ord, or sort based on the Hashvalue of the constructor) I think this would be difficult to implement efficiently. Another possibility would be to specify that the exception returned is the first one that would occur if the program is evaluated in according to some particular order of computation. This would be easy to implement efficiently. But it would break commutativity of `+' and similar operators. It would make it difficult for implementations to evaluate programs in any order other than that specified. This might make it signficantly more difficult for implementations to do things like automatically parallelize programs. For Mercury, for which similar issues arise, we went with a compromise: implementations are allowed to evaluate things in any order, but they must also provide a way for the user to request that things be evaluated strictly left-to-right. This allows the user to choose which is more important to them: efficiency or ease of debugging. The neat thing about this is that the exceptions can be *raised* in arbitrary purely functional code, without violating referential transparency. The question of which exception is chosen is done in the IO monad, where of course it is allowed to be non-deterministic. If you can do the above and you can stay consistent about which exceptions you return then you should be able to catch exceptions in arbitrary purely function code as well, right? If you used some canonical ordering on exceptions, then yes. But as I said above, I think that would be difficult to implement efficiently. The approach using an `NDSet' monad that I outlined in another post allows you to catch exceptions in arbitrary purely functional code, and is much easier to implement efficienctly. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: FW: Exceptions are too return values!
On 10-Jun-1998, S. Alexander Jacobson [EMAIL PROTECTED] wrote: On Thu, 11 Jun 1998, Fergus Henderson wrote: It would make debugging easier if the exception picked was consistent accross implementations. It doesn't matter which one, but it does matter that it is the same. (maybe you require that Exceptions implement Ord, or sort based on the Hashvalue of the constructor) I think this would be difficult to implement efficiently. Why is this difficult to implement efficiently? If exceptions are strings (as they appear to be in Simon's initial proposal), then you can sort alphabetically. If they have type, then they would be required to implement Has_HashValue and the implementation could rely on existential types to sort. I am sure there are better ways, but It is not obvious why the objection to cannonical ordering should be implementation efficiency... Efficient implementations may be possible, I just don't think they are easy. The problem is not the cost of the comparisons -- after all, that only occurs in the exceptional case. The problem I'm worried about is the code size and run time cost of installing the handler in the first place. For many common implementation models, every function such as `+' that is strict in two or more arguments will need to install an exception handler. This could be significant overhead, and furthermore it is overhead that you will pay even if your program doesn't make use of exceptions. Suppose, for argument's sake, that you are compiling to Java. Then the code for integer plus might look something like this int plus(HaskellInt arg1, HaskellInt arg2) { try { int arg1_val = arg1.eval(); } catch (Exception e1) { try { int arg2_val = arg2.eval(); } catch (Exception e2) { throw (e1 e2) ? e1 : e2; } throw e1; } int arg2_val = arg2.eval(); return arg1_val + arg2_val; } whereas with the nondeterministic model the code could be just int plus(HaskellInt arg1, HaskellInt arg2) { int arg1_val = arg1.eval(); int arg2_val = arg2.eval(); return arg1_val + arg2_val; } The latter will be cheaper, and the difference may be significant. -- Fergus Henderson [EMAIL PROTECTED] | "I have always known that the pursuit WWW: http://www.cs.mu.oz.au/~fjh | of excellence is a lethal habit" PGP: finger [EMAIL PROTECTED]| -- the last words of T. S. Garp.
Re: FW: Exceptions are too return values!
One of the wonderful things about functional languages is that they do not prescribe the order of evaluation. To achieve the effect you want would require us to completely prescribe that order, with very bad effects on efficiency. For example, consider ... But if we are required to ensure consistent choice of exception values then we can't do that any more, because the producer might evaluate the thunks in a different order to f. There is one aspect of Java that is relevant here: A Java implementation is free to load and link classes in any order, strictly or lazily, but it MUST report exceptions as if it had loaded and resolved the classes lazily. I think Haskell should have the same restriction: it would bad to receive different exceptions because a Haskell implementation decided to evaluate an argument strictly before it is needed. Java got that right. --Amr
Re: FW: Exceptions are too return values!
I was keeping quiet myself, because I am planning to write a paper touching on this topic. But the cat seems to be mostly out of the bag now, so I might as well pipe up. I'm glad you did. That's a neat idea. I'm familiar with the NDSet idea -- that's in the Hughes/O'Donnell paper that Kevin cited. The new thing you add is using the NDSet for the *exceptions*, rather than for the "main value". (It would be hopeless for every function that could raise an exception to get an NDSet in its result type, and hence required NDSet ops to manipulate.) I'll need to think more about this. Have you got a paper on the way? Simon
Re: FW: Exceptions are too return values!
Just to reiterate. I strongly urge you to ensure consistent exception behavior. As a matter of course, two different compiles should not result in two different programs. One of the wonderful things about functional languages is that they do not prescribe the order of evaluation. To achieve the effect you want would require us to completely prescribe that order, with very bad effects on efficiency. For example, consider f :: [Int] - Int Suppose that an analyser figures out that f evaluates every element of its argument list to produce its result. Then it is OK for the producer of the list to evaluate those thunks right away, rather than building thunks for f to evaluate. But if we are required to ensure consistent choice of exception values then we can't do that any more, because the producer might evaluate the thunks in a different order to f. This is a big issue for a lazy language. I really think that the thing to do is leave it unspecified which exception is chosen. In practice, only changing the compiler's optimisation level is likely to change the program's exception behaviour. Simon As a matter of course, should we assume that these extensions (exceptions, existentials) will become part of Haskell or are they just part GHC? Will they be part of Hugs? Hugs and GHC will be consistent. Whether it's a feature deemed worthy of being Officially Incorporated into Haskell is not something we'll know for a while. It's much more likely to be so incorporated if its implemented and found useful, though.
FW: Exceptions are too return values!
x= 1/0 - NaN (I guess the - is supposed to be a --) Actually, IEC 559 (a.k.a. IEEE 754, commonly referred to as 'IEEE floating point') specifies that 1 floating point divided by (positive) 0 shall have the 'continuation value' of *positive infinity*, and (if trapping is off) one shall record that a divide-by-zero has occurred. The recording shall be such that it can be read and reset at a later time (i.e. it shall not be reset automatically by the next f.p. operation). The default is to continue (arithmetic) calculations, even if an exception occurs, though one can in principle request trapping. Trapping, which in the IEEE sense can return back to the original calculation, is rarely accessible from programming languages, except assembler, as of yet. Admittedly IEC 559 does not pay any attention to lazy purely functional programming languages... fun:: Either a MyException - Either b MyException - Either c MyException ... propagate2 :: a - b - c - (Either a d - Either b d - Either c d) Here it is suggested that one EITHER gets a 'proper value', OR an 'exception'. But *closer* to the IEEE model of exceptions (most of the examples, at least, emanate from arithmetic), if one ignores trapping, is to give as return value BOTH a 'proper' value AND an exception collection (usually empty, or contain just the value 'inexact'). Like e.g. (I'm *not* suggesting anything for any programming language here): add :: (Float, Exceptions) - (Float, Exceptions) - (Float, Exceptions) Even if the 'Exceptions' values for both arguments are empty, a floating point addition may overflow. The resulting Float will then contain either the value +infinity or -infinity (as appropriate according to IEC 559), and the resulting Exceptions part would have a value indicating that an overflow has occurred. (More commonly, a floating point addition may be inexact, which can also be recorded as an exception in this way.) (Strict) arithmetic operations would then, trying to follow the IEEE model, propagate the union of the arguments's exception collections, adding any new exceptions that may arise from performing the (IEEE) arithmetic operation on the Float parts of the arguments. /kent k
Re: Exceptions are too return values!
On Tue, 9 Jun 1998, Mariano Suarez Alvarez wrote: In a typed language, a function *cannot* be applied to something outside its domain. That's the whole point! That represents a certain degree of idealisation though? E.g., sqrt _as a (single valued) mathematical function_ has domain R^{=0}. Certainly I could define a constructed datatype which is exactly this set. But if I want to use machine floats the natural type is Float-Float. Type classes don't appear to help because a condition for type (i.e., set) membership like (=0) can't in general be decided at compile time. So I seem to be faced with the fact that my argument type is a superset of the domain, and I have to either check at run time or prove that only elements of the domain will ever actually turn up. Types catch lots of errors, but by no means all of them. (Presumably similar examples can occur by, e.g., defining size balanced trees as Tr a = Nd a (Tr a) (Tr a) | Lf which doesn't guarantee that a type-checking expression is necessarily size-balanced.) Or am I missing a terribly obvious point? cheers, dave email: [EMAIL PROTECTED] "Taught the wife some html. __Bad www.cs.bris.ac.uk/~tweed/pi.htm move__." -- Alan Cox work tel: not available
Re: FW: Exceptions are too return values!
Alastair Reid has been very quiet, so I'll pipe up for him. Here's a reasonable design for exceptions in Haskell: * A value of Haskell type T can be EITHER one of the values we know and love (bottom, or constructor, or function, depending on T), OR it can be a set of exceptional values. * raise :: String - a (raise s) returns a single exceptional value, named by string s * All strict operations (case, +, etc) return the union of the exceptional values returned by their strict arguments For example, if both arguments to "+" return an exceptional value then "+" returns both. Similarly, any strict context. * handle :: (String - IO a) - IO a - IO a (handle h a) tries to perform the action a. If doing so delivers a set of exceptional values then apply the exception handler h to the string that names one of them. It is not defined which of the exceptional values is picked. The neat thing about this is that the exceptions can be *raised* in arbitrary purely functional code, without violating referential transparency. The question of which exception is chosen is done in the IO monad, where of course it is allowed to be non-deterministic. The implementation does not keep sets of exceptional values, of course. It simply propagates the first one it trips over to the nearest enclosing handler. (It is likely that successive runs will actually give the same behaviour, but recompiling the program with (say) different optimisation levels might change the order of evaluation, and hence change which exception is tripped over first.) We're implementing an experimental version of this in GHC, integrated with the IO monad exceptions, so that handle :: (IOError - IO a) - IO a - IO a and we add an extra constructor (UserError String) to the IOError type for exceptions raised by raise. Calls to "error" also show up as an exceptional value, of course. One merit of the system is that it chops out a tremendous number of run-time error checks in the IO monad, since we are now free to implement the mechanism with standard stack-unwinding techniques. Result: much better I/O performance. I'd be interested to know what people think of this. Simon
Re: FW: Exceptions are too return values!
* raise :: String - a * handle :: (String - IO a) - IO a - IO a I'd be interested to know what people think of this. I like the trick of handle being in the IO monad to avoid problems with evaluation order. As usual though, it can be a high price to pay if all you wanted was a little local error handling. I'll probably add it to hbc. -- Lennart
Re: FW: Exceptions are too return values!
I'd be interested to know what people think of this. Here's a reasonable design for exceptions in Haskell: ... The neat thing about this is that the exceptions can be *raised* in arbitrary purely functional code, without violating referential transparency. The question of which exception is chosen is done in the IO monad, where of course it is allowed to be non-deterministic. The implementation does not keep sets of exceptional values, of course. It simply propagates the first one it trips over to the nearest enclosing handler. That's neat indeed. What is especially nice is the ability to catch `error' exceptions. We're implementing an experimental version of this in GHC, integrated with the IO monad exceptions, so that handle :: (IOError - IO a) - IO a - IO a and we add an extra constructor (UserError String) to the IOError type for exceptions raised by raise). The fact that the type of exceptions is fixed (`String' or `IOError') is a weak point of the design (compared to Standard ML). It forces the programmer to encode exceptions as strings which is not what I would call elegant. [I weakly recall that there was a discussion on this point some years ago.] However, I see no way to improve the design :-( other than extending Haskell (with extensible sum types a la SML's `exception' declaration). One merit of the system is that it chops out a tremendous number of run-time error checks in the IO monad, since we are now free to implement the mechanism with standard stack-unwinding techniques. Result: much better I/O performance. I love performance gains ;-). Cheer, Ralf
Re: FW: Exceptions are too return values!
Thats a wonderful idea. With that it will be so much easier to write robust code without bloating the code with error checks. I've always been annoyed that I couldn't trap arbitrary errors, say to close down the application cleanly. Now, we only need extendible data types, and then we have an (almost) ML-like exception system :) /Tommy Simon L Peyton Jones wrote/a ecrit/skrev: Alastair Reid has been very quiet, so I'll pipe up for him. Here's a reasonable design for exceptions in Haskell: * A value of Haskell type T can be EITHER one of the values we know and love (bottom, or constructor, or function, depending on T), OR it can be a set of exceptional values. * raise :: String - a (raise s) returns a single exceptional value, named by string s * All strict operations (case, +, etc) return the union of the exceptional values returned by their strict arguments For example, if both arguments to "+" return an exceptional value then "+" returns both. Similarly, any strict context. * handle :: (String - IO a) - IO a - IO a (handle h a) tries to perform the action a. If doing so delivers a set of exceptional values then apply the exception handler h to the string that names one of them. It is not defined which of the exceptional values is picked. The neat thing about this is that the exceptions can be *raised* in arbitrary purely functional code, without violating referential transparency. The question of which exception is chosen is done in the IO monad, where of course it is allowed to be non-deterministic. The implementation does not keep sets of exceptional values, of course. It simply propagates the first one it trips over to the nearest enclosing handler. (It is likely that successive runs will actually give the same behaviour, but recompiling the program with (say) different optimisation levels might change the order of evaluation, and hence change which exception is tripped over first.) We're implementing an experimental version of this in GHC, integrated with the IO monad exceptions, so that handle :: (IOError - IO a) - IO a - IO a and we add an extra constructor (UserError String) to the IOError type for exceptions raised by raise. Calls to "error" also show up as an exceptional value, of course. One merit of the system is that it chops out a tremendous number of run-time error checks in the IO monad, since we are now free to implement the mechanism with standard stack-unwinding techniques. Result: much better I/O performance. I'd be interested to know what people think of this. Simon
Re: FW: Exceptions are too return values!
Simon and Alastair, This sounds like what I wanted. Just a few questions: * A value of Haskell type T can be EITHER one of the values we know and love (bottom, or constructor, or function, depending on T), OR it can be a set of exceptional values. * raise :: String - a (raise s) returns a single exceptional value, named by string s I presume that the programmer does not need to declare that a function may throw an exception ... that it is inferred from the "raise" in the function. Furthermore, I assume that you can use the return value in another function without unwrapping it e.g. divide x y = if y==0 then raise DivideByZeroException divideCaller x y = 1.0 + (divide x y) Could you give a code example to clarify if I am mistaken? * All strict operations (case, +, etc) return the union of the exceptional values returned by their strict arguments For example, if both arguments to "+" return an exceptional value then "+" returns both. Similarly, any strict context. It would make debugging easier if the exception picked was consistent accross implementations. It doesn't matter which one, but it does matter that it is the same. (maybe you require that Exceptions implement Ord, or sort based on the Hashvalue of the constructor) * handle :: (String - IO a) - IO a - IO a (handle h a) tries to perform the action a. If doing so delivers a set of exceptional values then apply the exception handler h to the string that names one of them. It is not defined which of the exceptional values is picked. As noted above, there should be some cannonical order. But, again I am having a little trouble understanding what you are saying here. Can you give some example code? Also, why do you have String here? Why can't exceptions be typed like in Java? Maybe exceptions should be a class and you use existential types... (I just read a paper on polytypic functions last night...maybe those?) The neat thing about this is that the exceptions can be *raised* in arbitrary purely functional code, without violating referential transparency. The question of which exception is chosen is done in the IO monad, where of course it is allowed to be non-deterministic. If you can do the above and you can stay consistent about which exceptions you return then you should be able to catch exceptions in arbitrary purely function code as well, right? (yes, I know I am changing positions again, but I am still learning). (It is likely that successive runs will actually give the same behaviour, but recompiling the program with (say) different optimisation levels might change the order of evaluation, and hence change which exception is tripped over first.) Just to reiterate. I strongly urge you to ensure consistent exception behavior. As a matter of course, two different compiles should not result in two different programs. When you distribute your program to users, add features, and distribute again, you don't want to diagnose the same problem in two different ways in two different versions. It makes customer service just that much harder. We're implementing an experimental version of this in GHC, integrated with the IO monad exceptions, so that As a matter of course, should we assume that these extensions (exceptions, existentials) will become part of Haskell or are they just part GHC? Will they be part of Hugs? -Alex- ___ S. Alexander Jacobson i2x Media 1-212-697-0184 voice1-212-697-1427 fax
Re: FW: Exceptions are too return values!
On Wed, 10 Jun 1998, Simon L Peyton Jones wrote: | We're implementing an experimental version of this | in GHC, integrated with the IO monad exceptions, so that | | handle :: (IOError - IO a) - IO a - IO a | | and we add an extra constructor (UserError String) to the | IOError type for exceptions raised by raise. How about adding the following errors: data IOError = ... | HeapOverflowError | StackOverflowError This is very useful when you for example want to implement a Hugs like system in Haskell. Now your Haskell programs will be very unlikely to crash! Another question: Is "handle" strict in the following argument: handle :: (IOError - IO a) - IO a - IO a ^ (meaning: will "handle f (return bottom)" be bottom?) Regards, Koen. -- Koen Claessen, [EMAIL PROTECTED], http://www.cs.chalmers.se/~koen, Chalmers University of Technology.
RE: Exceptions are too return values!
It's nice to have SOME way of handling exceptions, but... The implementation does not keep sets of exceptional values, of course. It simply propagates the first one it trips over to the nearest enclosing handler. One argument that can be made in favour of a generalised more IEEE-like mechanism is that it is usually such a pain to handle an exception that propagates like this that one most often does not bother to handle it properly (i.e. try to continue with the task, which is usually the best thing to do; user hitting 'break' or 'esc' excepted, but those are not 'exceptions' in this sense). And in many cases, using some reasonable 'continuation value' (which have already been specified and widely implemented for f.p. arithmetic) and a set of notes on exceptions that have occurred, is sufficient and gives a nicer behaviour of the program. Say that the application is to produce a simple function curve, for a function given as argument, so only the type is known and no other properties. Say that it does this by computing a list of pairs later to be turned into a nice-looking graph. Say also that overflows occur, or out-of-domain-errors occur. Having these errors propagate up to an IO monad or similar for handling, then having to restart, in the handler, the graph calculation at the appropriate place is much more difficult to handle (and is likely not to be done, or to be done in a buggy way) than just plodding on as if (nearly) nothing out of the ordinary happened. I don't think that this situation is so exotic that one can safely ignore it. Indeed this behaviour has been specified as the default behaviour for IEEE f.p. And it is usually better to let the application continue as normal, as long as no very critical error has occurred. (Note that not even divide-by-zero is considered critical in the IEEE world. It does not even return a NaN, unless the numerator is also zero (or a NaN).) What one would need to do to obtain this, not that I'm suggesting it very strongly, would be to generalise the IEEE model of exceptions from f.p. arithmetic to values in general, including adding Not-a-Proper-Value (NaPV) values to each non-f.p. datatype. A value of Haskell type T can be one of the values we know and love (bottom, or constructor, or function, depending on T), or NaPV (except for f.p. datatypes which already have NaN values) AND, implicitly, it has a set of exception values (this set is bottom if the value part is bottom). Strict operations would propagate exceptions and NaPV values. Certain predefined functions would be allowed to "read", or "replace" the exception set part, something like: add_exceptions :: a - Exceptions - a where the Exceptions would be built into the result, and read_and_clear_exceptions :: a - (a - Exceptions - b) - b where the function argument would be given the value with cleared Exceptions part, and as a second argument, the given Exceptions part. And all is purely functional... (Unless I missed something.) Yes, I have been ignoring performance issues. Generating and keeping Exceptions values around everywhere can be very taxing. It would be helpful to have an easy way of saying that the Exceptions part need not be built-into the value (effectively clearing it, though the proper Exceptions are propagated), changing the underlying datatype and delaying the building-in. It still may not be easy to get this very efficient. Indeed in the imperative (and IEEE arithmetic)world it is never built-in, but one has one exceptions value per thread 'on the side' (limited to arithmetic exceptions). (One such value per process is not sufficient these days when multi-threaded processes are used.) /kent k
Re: FW: Exceptions are too return values!
At 2:40 pm 10/6/98, Simon L Peyton Jones wrote: Here's a reasonable design for exceptions in Haskell: * A value of Haskell type T can be EITHER one of the values we know and love (bottom, or constructor, or function, depending on T), OR it can be a set of exceptional values. I'd be interested to know what people think of this. The error values and raising are similar to my PhD thesis (the type domains weren't a big problem -- it's technically straightforward [though perhaps philosophically debatable] to add additional values to a domain if you wish, and preserve these through other domains)! [For the record, this was in an early SML, and the current SML design is broadly similar to this, but with extendible exceptions (and a fixed evaluation order) as Ralf Hinze notes]. The non-deterministic handling is interesting and eliminating the error checks is a good thing, of course. One issue in my system was what to do for partial applications (do they return an exception instantly or only when fully applied -- I suppose in this setting you return the union of the possibilities and do whatever's convenient in the implementation). You also have to worry about what to when an exception raises an exception when you try to look at it in the handler (probably raised to a higher-level rather than handled locally). A parallel setting (one of my PhD motivations) would need a little care to avoid invoking the same handler multiple times if several threads raised exceptions. One thing you haven't noted: doesn't this also work for user interrupts too (Control-C) -- presumably there's no reason why it shouldn't (it did in my setting). Incidentally, why have you put the handler first? It stops you writing e `handle` h as with catch, and I'd expect would make it a bit harder to convert code? Regards, Kevin -- Division of Computer Science, Tel: +44-1334 463241 (Direct) School of Mathematical Fax: +44-1334 463278 and Computational Sciences,URL: http://www.dcs.st-and.ac.uk/~kh/kh.html University of St. Andrews, Fife, KY16 9SS.
Re: FW: Exceptions are too return values!
On Thu, 11 Jun 1998, Fergus Henderson wrote: It would make debugging easier if the exception picked was consistent accross implementations. It doesn't matter which one, but it does matter that it is the same. (maybe you require that Exceptions implement Ord, or sort based on the Hashvalue of the constructor) I think this would be difficult to implement efficiently. Why is this difficult to implement efficiently? If exceptions are strings (as they appear to be in Simon's initial proposal), then you can sort alphabetically. If they have type, then they would be required to implement Has_HashValue and the implementation could rely on existential types to sort. I am sure there are better ways, but It is not obvious why the objection to cannonical ordering should be implementation efficiency... -Alex- ___ S. Alexander Jacobson i2x Media 1-212-697-0184 voice1-212-697-1427 fax
Re: Exceptions are too return values!
On Mon, 8 Jun 1998, S. Alexander Jacobson wrote: 1. it is not logically consistent to treat exceptions as return values A function cannot do anything but return a value, can it? For example, suppose that we define a new function: foo' a b = a + b -- foo' is strict in its arguments Our intuition is that foo' is commutative. foo' a b = foo' b a. But that turns out not to be true when you have exceptions. That's the problem with intuitions: they can be wrong... Anyhow, if one is to have exceptions procteting +, I don't think that commutativity of foo' is reasonable: to handle exceptions, you have to do checks, and that you can only do in one order or another. Take x and y from before, z = foo' x' y' What is the value of z? Haskell does not promise to evaluate arguments in any particular order so, depending on implementation, z may be either Exception DivideByZero or Exception NotFactorialDomain -1. Actually, using a monad to manage exceptions you can (maybe, have to) choose a definite order of evaluation of non-exceptionality-conditions. Truly exceptional conditions are those that truly are outside of the domain of the function being evaluated. e.g. factorial -1 The VALUE of (factorial -1) is not an exception. Neither is the value of (factorial (1 `div` 0)). When a function is passed bad arguments, it is not meaningful (from a functional perspective) to have it return a value. In a typed language, a function *cannot* be applied to something outside its domain. That's the whole point! The value of a function over arguments outside its domain is undefined. When such an event occurs, the logically consistent behavior is to exit function evaluation and tell the caller what was wrong with the arguments passed (to the extent it is possible to do). One can rightfully argue that, if one is willing to consider bottom (which is a value we cannot test for!) a return value, which we are, considering an exception a return value is *very* consistent. -- m --- Mariano Suarez Alvarez The introduction of Departamento de Matematica numbers as coordinates Universidad Nacional de Rosario [...] is an act of violence Pellegrini 250 A. Weyl 2000 Rosario - Argentina e-mail: [EMAIL PROTECTED] ---
Re: Exceptions are too return values!
Alex Jacobson: Ooops, I forgot to remove the "and". Anyway, my point is that 1. it is not logically consistent to treat exceptions as return values 2. as an implementation matter it violates laziness to do so OK, now I follow. And diagree. ;-) On your second point first: I'm not sure what you mean by "violates laziness"; it would be true to say that adding exceptional return values in a given way might well reduce the laziness of the program; but this can always be obviated, given sufficient care. Using error-monad syntax might be a bit more palatable, but amounts to essentially the same thing. Alternatively, you can define HOFs to "lift" an n-ary function to an exception-propagating equivalent: That is what I did with my Exception version 2 syntax. The problem is that doing this lifting ends up being non-lazy. You're right, it alters the strictness of the program. But one can recover the original behaviour by delaying/eliminating the pattern-match, though I again I agree this is a pain. You could argue that this problem is an artifact of the Haskell syntax and that we could add Exceptions to the Thunk to achieve the desired result (treating exceptions as return values). I don't think I would, though! (If I understand what you mean by this.) The "problem" is an artifact of wanting to keep the language referentially transparent, which a built-in throw/catch scheme of the sort you suggest would scupper. Our intuition is that foo' is commutative. foo' a b = foo' b a. But that turns out not to be true when you have exceptions. That's true. And it remains true _however_ one treats exceptions. There's no way around that in general, I'm afraid, and I'll cite you assorted papers on Observable Sequentiality if you really want the grubby details. But the real point here is that Exceptions are, by definition, results that are outside the domain of the function being evaluated. Treating exceptions as algebraic types may be comforting, but what you are really doing in that case is extending the domain of your function Effectively, yes. From a domain theory PoV, this is all that one could possibly ever do, in fact ('error' included). -- and there are limits to how far you can go with that. These being? Truly exceptional conditions are those that truly are outside of the domain of the function being evaluated. e.g. factorial -1 The VALUE of (factorial -1) is not an exception. Neither is the value of (factorial (1 `div` 0)). When a function is passed bad arguments, it is not meaningful (from a functional perspective) to have it return a value. The value of a function over arguments outside its domain is undefined. When such an event occurs, the logically consistent behavior is to exit function evaluation and tell the caller what was wrong with the arguments passed (to the extent it is possible to do). I don't find this argument at all compelling. If a value is "truly outside the domain of the function being evaluated", then don't pass it to it! This may seem glib, but I do believe that its better SE practice in general. If there are exceptional conditions in "the world", or if determining a sufficient precondition is not practicable, then I repeat my advice concerning exceptional return values, a necessary evil though they might be. Right now that means using the error function. I am just saying that error isn't really enough for a production quality language. Agreed. Does this make more sense? It makes perfect sense, but I think that having exceptions as a language mechanism in Haskell is not realistic or viable, for the reasons I outlined before. I don't pretend that the alternatives are trivial, or even necessarily very pleasant-looking -- just that they're necessary. Slainte, Alex.
Exceptions are too return values!
Alex Jacobson: In the backchannel, Alastair Reid and Adrian Hey, have convinced me that the return values of functions and cannot be represented using algebraic types. I'm not clear what you mean by this. I am not sure of syntax, but I am describing something like: fun:: a-b-c :: MYException a b c fun x y = if x/= then numerator/denominator else throw SomeException x where numerator = x denominator = y catch DivisionByZero=throw OtherException y Trouble is, implicit propagation of thrown exceptions in this sort of way ends up being non-referentially-transparent. So I don't think you'll find the Haskell committee rushing to embrace this solution (or if they do, they might have to poison the current Chairman, amongst others). I think you can adequately simulate exception handling of this sort in Standard Haskell, by using say the Either type (as Ralf Hinze suggests), which if you test for exceptions explicitly leads to something like: fun:: Either a MyException - Either b MyException - Either c MyException fun (Right numerator) (Right denominator) = if denominator /= 0 then Right (numerator/denominator) else Left DivisionByZero fun (Left err1) y = Left err1 fun x (Left err2) = Left err2 Using error-monad syntax might be a bit more palatable, but amounts to essentially the same thing. Alternatively, you can define HOFs to "lift" an n-ary function to an exception-propagating equivalent: propagate2 :: a - b - c - (Either a d - Either b d - Either c d) Hope that helps... Slainte, Alex.