From: Jere S. <xm...@xm...> - 2004-06-28 12:34:10
|
Hi, just a few thoughts on adding new modules to extlib. One useful feature would be a module that offers different kinds of iterators on number ranges. I've found myself writing some nasty for loops and I've been thinking that wouldn't it be useful is there was something like List.iter for a number range. Attached is a blanket base where to build more iterators (like floating point iterators with a given step or multidimenstional iterators). Also coming from a C++ background, I've found the lack of a scope finalizera bit disappointing. It would be nice to have a function such as this in extlib: -- (** [run f fend] calls the function [f] and runs [fend] when exiting scope (either via end of exectuion or an exception). If [fend] throws an exception, it's thrown instead of any exception thrown by [f]. *) let run f fend = try let x = f () in fend (); x with x -> fend (); raise x ;; -- It might also be feasible to make a version which ignores any exceptions thrown by fend. Anyhow, here's the NumIter module I propositioned earlier: -- NumIter.mli (** Numeric iterators. Contains functions to iterate over ranges of numbers. *) val iter : (int -> unit) -> int -> int -> unit (** [iter f s e] calls the function [f] for every number from [s] to [e]. *) val map_array : (int -> 'a) -> int -> int -> 'a array (** [map_array f s e] maps the number range to an array of values. *) val map_list : (int -> 'a) -> int -> int -> 'a list (** [map_list f s e] maps the number range to a list of values. *) -- -- NumIter.ml let iter f s e = let step = if s < e then 1 else -1 in let rec do_it v = f v; if v <> e then do_it (v+step) in do_it s ;; let map_array f s e = let amt = 1 + (abs (e-s)) in let step = if s < e then 1 else -1 in let do_it i = f (s + i*step) in Array.init amt do_it ;; let map_list f s e = Array.to_list (map_array f s e) ;; -- Of course the actual implementations should be optimized a bit more :). Also open for discussion is the fact if the range should be open or closed. The current implementation runs the function always at least once (if start and end are the same). This is the same behaviour as with for. But the numiters also iterate backwards (which is useful, as there is no for which runs downwards), so this might be an issue. Comments anyone? -- Jere Sanisalo [xm...@xm...] - http://www.xmunkki.org/ |
From: Richard J. <ri...@an...> - 2004-06-28 12:50:49
|
On Mon, Jun 28, 2004 at 03:33:56PM +0300, Jere Sanisalo wrote: > Hi, > > just a few thoughts on adding new modules to extlib. One useful feature > would be a module that offers different kinds of iterators on number ranges. > I've found myself writing some nasty for loops and I've been thinking that > wouldn't it be useful is there was something like List.iter for a number > range. Attached is a blanket base where to build more iterators (like > floating point iterators with a given step or multidimenstional iterators). I like the iterators. One gripe I have about them (and indeed about the List.iter and List.map functions built into the library) is that the parameter order is just plain wrong! It's much clearer to write: List.iter list (fun elem -> very long function definition ...) instead of: List.iter (fun elem -> very long function definition ...) list Because what you're iterating over ('list') gets lost in the second version. Why can't we also iterate over floats? I consider it a problem in OCaml that there is no for ... construct for floats. > Also coming from a C++ background, I've found the lack of a scope finalizera > bit disappointing. It would be nice to have a function such as this in > extlib: Normally this function is called 'finally'. Is this function already in ExtLib? A quick grep over my version shows that it is not. There is a rather convoluted version of finally defined at the end of this file: http://www.fungible.com/stream_fixed.ml Of course what would be really great (and probably not possible) would be to have a way to run a finalizer when a variable goes out of scope. This would allow us to create, eg., file descriptor objects which automatically close the underlying file descriptor at the earliest opportunity. Rich. -- Richard Jones. http://www.annexia.org/ http://www.j-london.com/ Merjis Ltd. http://www.merjis.com/ - improving website return on investment MAKE+ is a sane replacement for GNU autoconf/automake. One script compiles, RPMs, pkgs etc. Linux, BSD, Solaris. http://www.annexia.org/freeware/makeplus/ |
From: Jere S. <xm...@xm...> - 2004-06-28 13:06:51
|
On Mon, Jun 28, 2004 at 01:50:39PM +0100, Richard Jones wrote: >One gripe I have about them (and indeed about the List.iter and >List.map functions built into the library) is that the parameter order >is just plain wrong! It's much clearer to write: >List.iter list (fun elem -> > very long function definition > ...) >instead of: >List.iter (fun elem -> > very long function definition > ...) list >Because what you're iterating over ('list') gets lost in the second >version. Yes I agree about this. But as we have no control over the List module, I thought it might be good to preserve the ordering for everything to feel the same. Not sure whether this is wise, though. >Why can't we also iterate over floats? I consider it a problem in >OCaml that there is no for ... construct for floats. As I said, iterators for floats could (should) be written. Also iterators for integers with a given step. I think the reason why most languages don't have automatic range iterators for floats is that what is the step taken? Is the last value also iterated if the step goes over it? Like "iterate from 0.1 to 0.3". If the step is 1.0, 0.1 should be iterated, but how about 0.3? So in our version the step at least should be given by the caller. And perhaps an optional bool flag for whether to force the evaluation of the end value (if the step does not hit it perfectly). >Normally this function is called 'finally'. Is this function already >in ExtLib? A quick grep over my version shows that it is not. I cuoldn't find it either, that's why I brought it up. Perhaps someone with cvs access could add it? Also what should the module be named? Are there other useful functions such as this which could fit the same family of functions? Or do we blindly add it to the Std -module? >Of course what would be really great (and probably not possible) would >be to have a way to run a finalizer when a variable goes out of scope. >This would allow us to create, eg., file descriptor objects which >automatically close the underlying file descriptor at the earliest >opportunity. True enough. But wrapping the file close to a finally function should do the trick in most cases. Of course it still forces the programmer to remember that file handles should be closed, but better than writing a custom finally construct every time :). -- Jere Sanisalo [xm...@xm...] - http://www.xmunkki.org/ |
From: Nicolas C. <war...@fr...> - 2004-06-28 14:05:47
|
> >Normally this function is called 'finally'. Is this function already > >in ExtLib? A quick grep over my version shows that it is not. > > I cuoldn't find it either, that's why I brought it up. Perhaps someone with > cvs access could add it? Also what should the module be named? Are there > other useful functions such as this which could fit the same family of > functions? > > Or do we blindly add it to the Std -module? Yes we do :) It's now added : val finally : (unit -> unit) -> ('a -> 'b) -> 'a -> 'b (** finally [fend f x] calls [f x] and then [fend()] even if [f x] raised an exception. *) let finally handler f x = let r = ( try f x with e -> handler(); raise e ) in handler(); r Please notice that I implemented a little more general version of it, IMHO it's more powerful this way. > >Of course what would be really great (and probably not possible) would > >be to have a way to run a finalizer when a variable goes out of scope. > >This would allow us to create, eg., file descriptor objects which > >automatically close the underlying file descriptor at the earliest > >opportunity. > > True enough. But wrapping the file close to a finally function should do the > trick in most cases. Of course it still forces the programmer to remember > that file handles should be closed, but better than writing a custom finally > construct every time :). The "out of scope" definition is a little tricky. For example, the bytecode interpreter is maintaining a stack so if you declare several let's you'll push on the stack an get the excepted behavior of "out of scope". But ocamlopt might carry some values directly into registers, and then they can be "lost" and garbaged while they would have still been on the stack of the bytecode interpreter. One other possibility is to add finalization function on channels when collected. This can be done by wrapping it into an object. Regards, Nicolas Cannasse |
From: Markus M. <ma...@oe...> - 2004-06-28 14:39:41
|
On Mon, 28 Jun 2004, Nicolas Cannasse wrote: > let finally handler f x = > let r = ( > try > f x > with > e -> handler(); raise e > ) in > handler(); > r And what if the handler raises an exception in the first case? It's often also more convenient to let the handler take the same argument as the function. I usually use the following function: let protect f x finally = let res = try f x with exc -> (try finally x with _ -> ()); raise exc in finally x; res The signature is: val protect : ('a -> 'b) -> 'a -> ('a -> unit) -> 'b Then you can write e.g.: let f ic = ... in protect f (open_in "foo") close_in if you want to guarantee that opened channels are closed. > > >Of course what would be really great (and probably not possible) would > > >be to have a way to run a finalizer when a variable goes out of scope. > > >This would allow us to create, eg., file descriptor objects which > > >automatically close the underlying file descriptor at the earliest > > >opportunity. But what if an exception is raised during finalization? > One other possibility is to add finalization function on channels when > collected. This can be done by wrapping it into an object. Or putting them into finalized structures. But I'm not a friend of this: it becomes fairly difficult to predict when some channel will be closed. I think that wrapper functions as discussed above are still the best thing you can do. Regards, Markus -- Markus Mottl http://www.oefai.at/~markus ma...@oe... |
From: Martin J. <mar...@em...> - 2004-06-28 17:17:43
|
On Mon, 28 Jun 2004, Markus Mottl wrote: > On Mon, 28 Jun 2004, Nicolas Cannasse wrote: > > let finally handler f x = > > let r = ( > > try > > f x > > with > > e -> handler(); raise e > > ) in > > handler(); > > r > > And what if the handler raises an exception in the first case? It's often > also more convenient to let the handler take the same argument as the > function. I usually use the following function: > > let protect f x finally = > let res = > try f x > with exc -> > (try finally x with _ -> ()); > raise exc in > finally x; > res This makes me feel like we will not know if the "finally block" has been completed or not. Why not totally prohibiting any exception raised from the "finally block"? let protect f x finally = let res = try f x with exc -> (try finally x with _ -> invalid_arg "protect") raise exc in (try finally x with _ -> invalid_arg "protect"); res Martin |
From: Markus M. <ma...@oe...> - 2004-06-28 17:34:12
|
On Tue, 29 Jun 2004, Martin Jambon wrote: > This makes me feel like we will not know if the "finally block" has been > completed or not. Why not totally prohibiting any exception raised from > the "finally block"? > > let protect f x finally = > let res = > try f x > with exc -> > (try finally x with _ -> invalid_arg "protect") > raise exc in > (try finally x with _ -> invalid_arg "protect"); > res But how do you know then that the exception came from "finally" and not from "f"? You could even lose the information, whether "f" was executed successfully at all! Regards, Markus -- Markus Mottl http://www.oefai.at/~markus ma...@oe... |
From: skaller <sk...@us...> - 2004-06-29 01:03:00
|
On Tue, 2004-06-29 at 03:34, Markus Mottl wrote: > On Tue, 29 Jun 2004, Martin Jambon wrote: > > This makes me feel like we will not know if the "finally block" has been > > completed or not. Why not totally prohibiting any exception raised from > > the "finally block"? > > > > let protect f x finally = > > let res = > > try f x > > with exc -> > > (try finally x with _ -> invalid_arg "protect") > > raise exc in > > (try finally x with _ -> invalid_arg "protect"); > > res > > But how do you know then that the exception came from "finally" and not > from "f"? You could even lose the information, whether "f" was executed > successfully at all! In general, I think exceptions are a bad idea. I spend a lot of time removing them from my code :( In a typical case it goes like this: try let f = open_in fname in do_something f; close_in f with _ -> () Only this is very BAD code because it fails to isolate two completely distinct possibilities for an error: in opening the file, or in processing it: in the latter case the file isn't closed: we didn't actually expected a processing error here, and also fail to correctly re-raise the exception. This lack of localisation is quite hard to fix: let f = try open_in fname with _ -> () in do_something f; close_in f WOOPS! that's a type error, which is good, because we can't 'do_something' if the file wasn't opened. What else can we try? try let f = open_in fname in begin try do_something f with _ -> close_in f end; close_in f; with _ -> failwith "Do something failed" WOOPS, we tried to close f twice. We can only do this: exception Some_error try let f = open_in fname in begin try do_something f with _ -> close_in f; raise Some_error end; close_in f; with Some_error -> raise Some_error | _ -> () but now we have lost the location and kind of the error in something .. and we had to introduce a new global exception as well. The obvious way to fix this mess is to eliminate the undesirable exceptions early: let result = try Some (open_in fname) with _ -> None in match result with | None -> () | Some f -> begin try do_something with x -> close_in f; raise x end close_in f Of course, the result of the calculation here is unit, more generally we'd be saying let result = do_something in close_in f; result and obviously we'd be wrapping that as well. Generally, the exceptions discard localisation and destroy any kind of static assurance errors are handled. To me, the Haskell monadic approach seems a sounder. The problem with exceptions is that they're basically without solid theoretical principles. Throwing out context is one thing .. recovering at an unknown point, trapping unknown exceptions without any aid from the type system simply defeats the purpose of having a type system. As far as I can see, just about the only way to use exceptions properly is to eliminate them at the earliest possible point .. which suggests libraries simply shouldn't throw them. It seems difficult to escape from deep recursions without exceptions. They can be convenient. They're useful for rapid prototyping where you need a demo that works on some cases *fast*. And they seem convenient when you 'know' there can't be an error. The price is high. In avoiding statically handling false negatives (errors that can't occur) your code has become fragile: any change which may in fact lead to raising an error will not be discovered without testing.. and then it will be very hard to find. Would static exceptions be better? (Allow exceptions but require that they're caught in an ancestor of the scope they're raised in, and at least in a child of the scope the exception is declared in) Any comments on any of this appreciated. -- John Skaller, mailto:sk...@us... voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net |
From: Bardur A. <oca...@sc...> - 2004-06-29 15:36:41
|
On Tue, Jun 29, 2004 at 11:02:49AM +1000, skaller wrote: > On Tue, 2004-06-29 at 03:34, Markus Mottl wrote: > > On Tue, 29 Jun 2004, Martin Jambon wrote: > > > This makes me feel like we will not know if the "finally block" has been > > > completed or not. Why not totally prohibiting any exception raised from > > > the "finally block"? > > > > > > let protect f x finally = > > > let res = > > > try f x > > > with exc -> > > > (try finally x with _ -> invalid_arg "protect") > > > raise exc in > > > (try finally x with _ -> invalid_arg "protect"); > > > res > > > > But how do you know then that the exception came from "finally" and not > > from "f"? You could even lose the information, whether "f" was executed > > successfully at all! > > In general, I think exceptions are a bad idea. > I spend a lot of time removing them from my code :( > > In a typical case it goes like this: > > try > let f = open_in fname in > do_something f; > close_in f > with _ -> () > > > Only this is very BAD code because it fails to isolate > two completely distinct possibilities for an error: > in opening the file, or in processing it: in the latter > case the file isn't closed: we didn't actually > expected a processing error here, and also fail to > correctly re-raise the exception. Here, the issue really is that you're ignoring _any_ exception, not just exceptions which can only be raised by open_in. Of course, sometimes there may be cases where do_something can raise the same type of as your guarded expression ("open_in ..."), so in those cases you're right. You do not get any information about which function caused the exception to be raised. [--snip--] > try > let f = open_in fname in > begin > try do_something f > with _ -> close_in f > end; > close_in f; > with _ -> failwith "Do something failed" > > WOOPS, we tried to close f twice. We can only do this: What you really want here is a "finally" clause on "try ...": try let f = open_in fname in begin try do_something finally close_in f end; with _ -> failwith "Failed somewhere" It can ensure that close_in gets called exactly one regardless of how your exit from the code. Of course, your original exception information is lost, but again that's more a problem relating to the programmer completely ignoring the exception information than anything else; in code that I write I usually don't find that exceptions "overlap" in a way which causes problems like this; just as long as you only catch the specific exceptions raised by the specific functions you call, you're usually ok. [--snip--] > > The obvious way to fix this mess is to eliminate the > undesirable exceptions early: > > let result = > try Some (open_in fname) with _ -> None > in match result with > | None -> () > | Some f -> > begin try do_something with x -> close_in f; raise x end > close_in f > You again ignore any exception information by just using None, so I would suggest using a polymorphic variant like this: `Error exn `Done retval That way you can still get at the exception information. [--snip--] > > Generally, the exceptions discard localisation > and destroy any kind of static assurance errors are handled. Yup, you essentially pay for the convenience of not having to "decode" error values all the time by throwing out static guarantees about error handling and location information (but you can also exploit this do to things like the whole "Extra exn" which is used by the Uq_engines module of Equeue to provide user-extensibility of event handling code). It seems to me that there are a few reasons for this: *) Exceptions currently do not carry any useful __programmer-accessible__ information about where the exception occurred (i.e. stack contents). *) Few of the current StdLib exceptions carry enough semantic information about the exception to debug sensibly, e.g. the Invalid_arg exception doesn't mention _which_ variable had an invalid value, what the invalid value was, etc. It's arguable how handlable an Invalid_arg is in general, but it would help greatly during debugging if such information were attached. It would also extend the "ignorability" of various exceptions to more specific cases (e.g. "ignore anything about Invalid_arg if it's the 3rd argument which is invalid"). *) Raiseable exceptions do not have to be declared. Although this may seem like B&D to some people, it does make it *much* easier to be disciplined and clear about what error information your code catches/handles/throws away. Unless you do (obviously) bad things like try do_something with _ -> do_something_else It is much easier to handle only specific exceptions which can actually be raised try do_something with Raisable1 | Raisable2 | Raisable3 -> do_something_else and the fact that this is verified at compile time helps the programmer greatly by forcing the programmer to actually handle all exceptions at some level. (Of course, one might want something similar to the RuntimeException in Java to avoid drowning in exception handling code for exceptions which are (should be) rare and are usually fatal and a clear sign of grave programmer error). [--snip--] > > The problem with exceptions is that they're basically > without solid theoretical principles. Throwing out > context is one thing .. recovering at an unknown > point, trapping unknown exceptions without any > aid from the type system simply defeats the purpose > of having a type system. > > As far as I can see, just about the only way to > use exceptions properly is to eliminate them > at the earliest possible point .. which suggests > libraries simply shouldn't throw them. I would tend to agree here; however, I think that there are valid reasons for the RuntimeExceptions of this world, e.g. it's quite hard to imagine code having to handle index-errors from Array.get directly. It would just swamp the code with irrelevant detail. > > It seems difficult to escape from deep recursions > without exceptions. They can be convenient. Well, I'm of the opinion that, although they solve this particular problem, what we really want is full support for continuations. :) > They're useful for rapid prototyping where you need a > demo that works on some cases *fast*. > > And they seem convenient when you 'know' there can't be > an error. The price is high. In avoiding statically > handling false negatives (errors that can't occur) your > code has become fragile: any change which may in fact > lead to raising an error will not be discovered without > testing.. and then it will be very hard to find. Agreed. > > Would static exceptions be better? (Allow exceptions > but require that they're caught in an ancestor of the > scope they're raised in, and at least in a child of > the scope the exception is declared in) I'd say yes, but the non-inheritance aspect of exceptions may be a concern with regard to the amount of essentially trivial error handling code. I'm still not convinced that it's not better to simply explicitly return `Success <value> `Failure <exception_type> from all functions which can potentially fail (in a non-RuntimeException kind of way). Given those and a "die-horribly-if-failure-but-return-result-otherwise" function and the "ignore" built-in error handling is much more explicit and arguably just as simple. Just my 2 cents. -- Bardur Arantsson <ba...@im...> <ba...@sc...> - Leela, save me! And yourself I guess... and my banjo... and Fry! Bender | Futurama |
From: Brian H. <bh...@sp...> - 2004-06-29 16:35:02
|
On Tue, 29 Jun 2004, Bardur Arantsson wrote: > *) Exceptions currently do not carry any useful > __programmer-accessible__ information about where the > exception occurred (i.e. stack contents). Defining what the stacks' contents are is tricky in the face of tail call optimization- the stack may have already be partially freed. Consider three functions, a, b, and c. a calls b in a tail all, and b calls c in a tail call. Function c throws an exception. You call a from function foo (not a tail call, so foo stays on the stack). Since they're tail calls, the stacks for a and b have already been freed by the time c throws an exception, so your stack makes it look like foo directly called c, which it didn't. So if, for example, c was called with a bad value, a stack trace won't help you find who the real culprit is. > > *) Few of the current StdLib exceptions carry enough > semantic information about the exception to debug > sensibly, e.g. the Invalid_arg exception doesn't mention > _which_ variable had an invalid value, This is a good point- something extlib could improve upon. Note that I don't consider error handling a performance critical path. Which means it'd be OK to do stuff like: if (arg1 < 0) || (arg1 > n) || (arg2 < 0) || (arg2 > n) then (* we have an invalid arg *) if (arg1 < 0) then invalid_arg (sprintf "Foo arg1 = %d, negative values not allowed!" arg1) else if (arg1 > n) invalid_arg (sprintf "Foo arg1 = %d, value larger than n = %d!" arg1 n) else ... else (* args are valid... *) For the critical path, this code is no slower than: if (arg1 < 0) || (arg1 > n) || (arg2 < 0) || (arg2 > n) then invalid_arg "Foo" (* hey, who needs debugging information? *) else ... Once we detect that we're going to throw an exception, spending some extra time deciding which exact exception we're going to throw isn't a problem. > *) Raiseable exceptions do not have to be declared. > Although this may seem like B&D to some people, it does > make it *much* easier to be disciplined and clear about > what error information your code catches/handles/throws > away. The unanimous opinion of all the Java programmers I've talked to is that, while this looks good on paper, it's a royal pain in the kiester in practice. Ocaml's strong type checking works because it's much easier to work with the type system than it is to subvert it. Type inference might releive some of the pain, but Ocaml's inference isn't complete enough. In the Java world, you do see a lot of code like: try { ... } catch (Exception e) { /* Do nothing- I just don't feel like writting a throws clause */ } This is worse than the current problems. We'd be back in the bad old days of programs ignoring error return values. I'd much rather have uncaught exceptions than discarded exceptions. > (Of course, one might want something similar to the > RuntimeException in Java to avoid drowning in exception > handling code for exceptions which are (should be) rare > and are usually fatal and a clear sign of grave programmer > error). Yep. At which point everyone inherits their exceptions from RuntimeException, wether they're rare or not. > I'm still not convinced that it's not better to simply > explicitly return > > `Success <value> > `Failure <exception_type> > > from all functions which can potentially fail (in a > non-RuntimeException kind of way). Given those and a > "die-horribly-if-failure-but-return-result-otherwise" > function and the "ignore" built-in error handling is much > more explicit and arguably just as simple. This is, vaguely, what C does. At which point error handling strategies fall into two broad categories: 1) ignore the error, and fail in some later spot of the code (generally by a segv) in a way that gives you absolutely no error recovery or debugging information. 2) Print an error message and exit the program. Which is the default behavior of uncaught exceptions. -- "Usenet is like a herd of performing elephants with diarrhea -- massive, difficult to redirect, awe-inspiring, entertaining, and a source of mind-boggling amounts of excrement when you least expect it." - Gene Spafford Brian |
From: skaller <sk...@us...> - 2004-06-30 09:42:47
|
On Wed, 2004-06-30 at 02:39, Brian Hurt wrote: > On Tue, 29 Jun 2004, Bardur Arantsson wrote: > > > *) Exceptions currently do not carry any useful > > __programmer-accessible__ information about where the > > exception occurred (i.e. stack contents). > > Defining what the stacks' contents are is tricky in the face of tail call > optimization- Oh come on .. lets generalise sensibly! Elide the words "tail call" here! Optimisations can do all kinds of interesting transformations. If exceptions are too heavily weighed down with location information, these optimisations would be defeated. My guess is a proposition: "The more location information/error detail in an exception the further you can throw it without losing control" with the obvious corrollary "If your exception carries limited information, it must be caught very locally (ie immediately)" This proposition suggests there is a balance between data complexity and control complexity, and exception handlers do control inversion -- convert complex data into complex control flows, and simple data into simple control flows. Raising exceptions does the opposite .. it converts complex flows into complex data, and simple flows into simple data. The assumption is such operations preserve information. EG if you have some function calls raising exceptions and you need to know which function failed you need a distinct exception for each one .. where 'distinct' means 'distinct with the scope of the handler' The problem then seems to be that 'the scope of the handler' is only connected to the code it is managing dynamically .. there's no static support as you'd get from a type system. -- John Skaller, mailto:sk...@us... voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net |
From: Bardur A. <oca...@sc...> - 2004-06-29 18:00:00
|
On Tue, Jun 29, 2004 at 11:39:52AM -0500, Brian Hurt wrote: > On Tue, 29 Jun 2004, Bardur Arantsson wrote: > > > *) Exceptions currently do not carry any useful > > __programmer-accessible__ information about where the > > exception occurred (i.e. stack contents). > > Defining what the stacks' contents are is tricky in the face of tail call > optimization- the stack may have already be partially freed. Consider > three functions, a, b, and c. a calls b in a tail all, and b calls c in a > tail call. Function c throws an exception. You call a from function foo > (not a tail call, so foo stays on the stack). Since they're tail calls, > the stacks for a and b have already been freed by the time c throws an > exception, so your stack makes it look like foo directly called c, which > it didn't. So if, for example, c was called with a bad value, a stack > trace won't help you find who the real culprit is. Dang, I forgot about tail-call optimization. :| [--snip--] > > *) Raiseable exceptions do not have to be declared. > > Although this may seem like B&D to some people, it does > > make it *much* easier to be disciplined and clear about > > what error information your code catches/handles/throws > > away. > > The unanimous opinion of all the Java programmers I've talked to is that, > while this looks good on paper, it's a royal pain in the kiester in > practice. Ocaml's strong type checking works because it's much easier to > work with the type system than it is to subvert it. Type inference might > releive some of the pain, but Ocaml's inference isn't complete enough. > > In the Java world, you do see a lot of code like: > > try { > ... > } > catch (Exception e) { > /* Do nothing- I just don't feel like writting a throws clause */ > } > > This is worse than the current problems. A Java programmer who does the above would also be liable to just use try do_something with _ -> () (Of course, sometimes this cannot always be done because of type constraints on the result value, but you get the idea...). The lesson here is that you can't fix broken programmers, but I agree that forcing exception declarations may make this more prevalent. I will say, though, that there is _NO_ excuse for not calling e.printStackTrace() and possibly System.exit() in a catch-all handler like the above. Another issue which makes the problem worse than it really needs to be (specifically in the Java standard library) is that the line between RuntimeExceptions and other exceptions is often badly drawn -- but of course you can't please everyone. > We'd be back in the bad old days of programs ignoring > error return values. I'd much rather have uncaught > exceptions than discarded exceptions. I'm afraid either alternative has drawbacks, but the current method certainly also has drawbacks for programmers who actually _want_ to be conscientious about their exception handling -- the biggest of which is that I cannot statically guarantee that all exceptions which can be raised are caught. But I recognize that there may be lots of potential issues with such "static exceptions", e.g. when using closures (you must require that closures be exception-compatible)... > > > (Of course, one might want something similar to the > > RuntimeException in Java to avoid drowning in exception > > handling code for exceptions which are (should be) rare > > and are usually fatal and a clear sign of grave programmer > > error). > > Yep. At which point everyone inherits their exceptions from > RuntimeException, wether they're rare or not. Surely, this is a symptom of "Stupid/Lazy Programmer's Syndrome" rather than some shortcoming of the approach itself...? The OCaml equivalent is never catching any exceptions, which happens all the time already, simply because the compiler cannot tell you which exceptions you _should_ catch -- and I (as the programmer) cannot be expected to spot and remember every possible exception that can be thrown from library code just from reading the library documentation (which may even be out of sync). As long as the standard library's exceptions are well-chosen (Runtime vs. "Compile-time-must-catch") there is not really much more can do. The programmer can always ignore errors, whether that be by ignoring exceptions or by ignoring return values. > > > I'm still not convinced that it's not better to simply > > explicitly return > > > > `Success <value> > > `Failure <exception_type> > > > > from all functions which can potentially fail (in a > > non-RuntimeException kind of way). Given those and a > > "die-horribly-if-failure-but-return-result-otherwise" > > function and the "ignore" built-in error handling is much > > more explicit and arguably just as simple. > > This is, vaguely, what C does. At which point error handling strategies > fall into two broad categories: > > 1) ignore the error, and fail in some later spot of the code (generally by > a segv) in a way that gives you absolutely no error recovery or debugging > information. But the type system in OCaml will often force you to do reasonably sensible things because of type unification, e.g. in the open_in example, you cannot just ignore the error, you _have_ to deal with it _somehow_ (even if it is just by using the default choke-on-errors strategy that is currently being used), otherwise your result value _will_ have the wrong type. So, with this approach you are essentially _forcing_ the programmer to "decode" the `Success ... or `Failure ... value at **some point** (note: not necessarily right at/after the call), thus forcing them to handle the error _somehow_ to get to the actual return value of the function. I strongly suspect that skaller is right, and that exceptions are not really the best way to do error handling. But maybe we'll just have to settle for exception handling as the "best of a bad bunch" of solutions. Oh, well. -- Bardur Arantsson <ba...@im...> <ba...@sc...> - No beer and TV make Homer something something. Homer Simpson | The Simpsons |
From: skaller <sk...@us...> - 2004-06-30 09:58:52
|
On Wed, 2004-06-30 at 03:59, Bardur Arantsson wrote: > > Yep. At which point everyone inherits their exceptions from > > RuntimeException, wether they're rare or not. > > Surely, this is a symptom of "Stupid/Lazy Programmer's > Syndrome" rather than some shortcoming of the approach > itself...?. Being lazy is a GOOD attribute for a programmer! If it is inconvenient to handle exceptions, errors, or write simple code simply, we, the programmers, must blame our programming language .. :) If you can see a pattern of encoding and you cannot automate it .. the programming language is inadequate. -- John Skaller, mailto:sk...@us... voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net |
From: skaller <sk...@us...> - 2004-06-30 09:00:53
|
On Tue, 2004-06-29 at 23:18, Bardur Arantsson wrote: > Here, the issue really is that you're ignoring _any_ > exception, not just exceptions which can only be raised by > open_in. Consider: try let f = open_in fname in let g = open_in gname in with Open_failure -> woops we don't know which file to close Refinement by trapping the *kind* of error provides no general solution .. elimination (by converting to a variant type) *is* a universal solution: it localises the cases *and* encodes it securely in the type system. Of course this is just the old fashioned C solution with type system support added. I think this is correct *but* clearly we desire to avoid all the housekeeping involved in checking the error at every branch point in the call chain. It seems Haskells monadic model provides a generic way to do this, but I have no experience with how easy that mechanism is to use: theoretically nice solutions are sometimes more clumbsy to use in practice than one would expect because theory is abstract whilst practice involves all the details. -- John Skaller, mailto:sk...@us... voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net |
From: Bardur A. <oca...@sc...> - 2004-06-30 09:56:03
|
On Wed, Jun 30, 2004 at 07:00:42PM +1000, skaller wrote: > On Tue, 2004-06-29 at 23:18, Bardur Arantsson wrote: > > > Here, the issue really is that you're ignoring _any_ > > exception, not just exceptions which can only be raised by > > open_in. > > Consider: > > try > let f = open_in fname in > let g = open_in gname in > with > Open_failure -> woops we don't know which file to close > > Refinement by trapping the *kind* of error provides no > general solution .. elimination (by converting to a variant > type) *is* a universal solution: it localises the cases *and* > encodes it securely in the type system. Of course this is just > the old fashioned C solution with type system support added. What you _should_ have done is, of course, try let f = open_in fname in in try let g = ... in with Open_failure -> .... with Open_failure -> ... and it *does* make sense to do it that way *in the context of exception handling*, because in the case of an exception, only one of the two open_in's can possibly have failed. But I find that this places an unnecessary burden on the programmer in that they must handle exceptions precisely where they are raised to avoid losing information about the location of the exception; this in turn leads to rather unwieldly syntax. Contrast the above with a made-up example with more explicit handling... let f = open_in fname in let g = open_in gname in match f,g with [ (`Success f, `Success g) -> do_something f g | _ -> () ]; close_in f; close_in g; I know, the match syntax is ugly, but with a bit of preprocessing that could easily be changed to something like: on_success f g ... { do_something f g }; which would work the same way as the match above. > > I think this is correct *but* clearly we desire to avoid > all the housekeeping involved in checking the error at > every branch point in the call chain. One option is a sort of hybrid approach where results are implicitly wrapped (boxed) in `Success x | `Failure exn (wherever exceptions can be raised at all) and where you can "lazily" query the success/failure status of a return value, thus: (failed x) ... which returns True iff evaluating x (a return value) will cause an exception to be raised. x ... evaluates to x (if x is internally `Success x) or raises the exception exn (if x is internally `Failure exn). This is essentially automatic un-boxing of `Success values when needed, but preserves exception-like semantics if you're not bothered about the explicit "exit status" of the functions. raise exn ... return (`Failure exn) immediately. Of course this means that all values must be internally represented by `Success x | `Failure exn | x I haven't fully thought out the semantics of this, but it seems workable to me. > It seems Haskells monadic model provides a generic way to > do this, but I have no experience with how easy that > mechanism is to use Alas, neither have I. -- Bardur Arantsson <ba...@im...> <ba...@sc...> - Look, this isn't an argument. - Yes, it is. - No, it isn't! It's just contradiction. - No, it isn't. Man and Mr. Vibrating | Monty Python's Flying Circus |
From: Richard J. <ri...@an...> - 2004-06-30 09:04:39
|
On Tue, Jun 29, 2004 at 03:18:01PM +0200, Bardur Arantsson wrote: > *) Raiseable exceptions do not have to be declared. > Although this may seem like B&D to some people, it does > make it *much* easier to be disciplined and clear about > what error information your code catches/handles/throws > away. Unless you do (obviously) bad things like If there's one thing I learned from my brief days doing Java programming, it's that checked exceptions are a really really bad idea. Rich. -- Richard Jones. http://www.annexia.org/ http://www.j-london.com/ Merjis Ltd. http://www.merjis.com/ - improving website return on investment C2LIB is a library of basic Perl/STL-like types for C. Vectors, hashes, trees, string funcs, pool allocator: http://www.annexia.org/freeware/c2lib/ |
From: skaller <sk...@us...> - 2004-06-30 09:28:12
|
On Tue, 2004-06-29 at 23:18, Bardur Arantsson wrote: > It is much easier to handle only specific exceptions which > can actually be raised > > try > do_something > with > Raisable1 > | Raisable2 > | Raisable3 -> > do_something_else > > and the fact that this is verified at compile time helps > the programmer greatly by forcing the programmer to > actually handle all exceptions at some level. I have toyed with Static Exception Handling (SEH). It seems to me SEH is a better way to handle a wide class of cases where we want 'alternate returns', as opposed to actual error handling. These are just nicely sugared gotos of course :) The question arises .. what fraction of uses of Dynamic Exception Handling (DEH) can be replaced by SEH? What's left over? > I would tend to agree here; however, I think that there > are valid reasons for the RuntimeExceptions of this world, > e.g. it's quite hard to imagine code having to handle > index-errors from Array.get directly. It would just swamp > the code with irrelevant detail. Would it? Of course, handling the exception with a match on every access manually might be a pain. Lets agree that this is too much housekeeping and obscures the 'normal flow of control'. But is DEH the only viable alternative? I can't believe that. I think we just haven't been creative enough. DEH is so grossly overpowered, it seems to fly in the face of strict static typing! > Well, I'm of the opinion that, although they solve this > particular problem, what we really want is full support > for continuations. :) That may be so! > I'd say yes, but the non-inheritance aspect of exceptions > may be a concern with regard to the amount of essentially > trivial error handling code. The ability to classify and/or subtype/group or more generally pattern match on exception *kind* is definitely worth researching .. I'd like to say this is a separable issue, except that I'm not sure it is :) I have a gut feeling that until we have polyadic programming technology widely available, we won't solve this one either: my guess is that exceptions are the residual which comes from the fact that most 'functions' are partial not total. So in handling functorial polymorphism where fold, map, etc, are single combinators working on all type functors (lists, trees, etc), the dualisation should yield a corresponding structure for exceptions. .. heh .. but it will take a real type theorist (which I not) to say this properly :) > I'm still not convinced that it's not better to simply > explicitly return > > `Success <value> > `Failure <exception_type> > > from all functions which can potentially fail (in a > non-RuntimeException kind of way). Given those and a > "die-horribly-if-failure-but-return-result-otherwise" > function and the "ignore" built-in error handling is much > more explicit and arguably just as simple. It's my observation too the DEH is only simple when used very lightly ... as soon as you have multiple libraries and have to design an architectural framework to support variant results, DEH seems to collapse as a 'convenience' and dominate the design and coding... that is, become *inconvenient* :) -- John Skaller, mailto:sk...@us... voice: 061-2-9660-0850, snail: PO BOX 401 Glebe NSW 2037 Australia Checkout the Felix programming language http://felix.sf.net |
From: Brian H. <bh...@sp...> - 2004-06-29 03:04:10
|
On 29 Jun 2004, skaller wrote: > In general, I think exceptions are a bad idea. > I spend a lot of time removing them from my code :( Looking at your examples, I think you're trying to fix something that doesn't need fixing. My general rule is "don't catch exceptions that you don't know how to handle". The default handling of uncaught exceptions isn't perfect, but it's orders of magnitude better than most program's error handling, and I can't think of a better way to handle things. Or, to put it a different way, exceptions are the worst possible way to handle errors, except for all the other ways. Certainly, quietly discarding exceptions is a real bad idea. Code like: try ... with | _ -> () is evil. Dropping certain exceptions, yes. But all exceptions? As for the theoretical foundations, I wouldn't be surprised if someone who follows the theory comes back with actual citations. I don't think the theoretical foundations would be that hard- exceptions are just an alternate return path. Actually, I go all types of expressions can be one of two things- either an exception or the unexceptional type. Then I start defining my basic operators to properly "pass along" an exception. So, for example, an if statement of the form "if expr1 then expr2 else expr3" would be evaluated like this: - if expr1 evaluates to an exception e, the entire expression evaluates to that exception - if expr1 evaluates to true, the expression evaluates to whatever expr2 evaluates to (this can be an exception) - if expr1 evaluates to false, the expression evaluates to whatever expr3 evaluates to. This keeps me firmly within strongly typed territory, as I understand things. Compared to monads, this is pretty trivial. -- "Usenet is like a herd of performing elephants with diarrhea -- massive, difficult to redirect, awe-inspiring, entertaining, and a source of mind-boggling amounts of excrement when you least expect it." - Gene Spafford Brian |
From: Richard C. <rj...@ii...> - 2004-07-11 14:21:02
|
When I read your examples I wondered how often this situation arises. Is it just a symptom of the file interface or is it more general? It occurs whenever resources, such as a file descriptor, need to be relinquished, which generally is pretty often. In one programming task that I worked on for some time most of the code followed the following pattern. 1. allocate resources 2. manipulate resources 3. free resources In each step you have to be ready for something to go wrong. Since it was in a distributed environment steps 1 2 and 3 were composed of concurrent parts, i.e. all resources were allocated concurrently. In other programming tasks it has not been important to catch errors because the effect of the programming stopping or leaking a few resources is not serious. But if you have to catch the errors it is tedious and error prone to write massive try catch blocks to catch every error. Our solution was to use the command pattern. type success_or_failure = Success | Failure class type command = method do: clipboard -> success_or_failure method undo: clipboard -> unit method final: clipboard -> unit end ;; Then programs became the construction of object like the following: let copy_file_cmd_name_args src dst = let src_cmd = open_file_cmd src in let dst_cmd = open_file_cmd dst in seq_execute_cmd [ par_execute_cmd [src_cmd, dst_cmd], copy_file_cmd_fd_func_args src_cmd#file dst_cmd#file ]; Here serial_execute runs through the commands executing them one by one until either it finishes or an error is generated in which case it backs out by calling undo. Finally objects are given a chance to release resources when final is called. This way we were sure that resources would be properly accounted for. Of course hardware faults or mis-behaving clients can still cause you trouble. Particularly tracing which operation first caused a failure and what conditions lead to that failure. It seems to me that if you have to catch errors then you have to construct some similar framework in Ocaml. There's no language support for writing commands. regards, Richard. |
From: Martin J. <mar...@em...> - 2004-06-29 14:50:45
|
On Mon, 28 Jun 2004, Markus Mottl wrote: > On Tue, 29 Jun 2004, Martin Jambon wrote: > > This makes me feel like we will not know if the "finally block" has been > > completed or not. Why not totally prohibiting any exception raised from > > the "finally block"? > > > > let protect f x finally = > > let res = > > try f x > > with exc -> > > (try finally x with _ -> invalid_arg "protect") > > raise exc in > > (try finally x with _ -> invalid_arg "protect"); > > res > > But how do you know then that the exception came from "finally" and not > from "f"? You could even lose the information, whether "f" was executed > successfully at all! I would specify that "finally" should not raise any exception. The Invalid_argument exception is not supposed to be raised if the program was written correctly, right? Martin |
From: Markus M. <ma...@oe...> - 2004-06-29 16:44:27
|
On Tue, 29 Jun 2004, Martin Jambon wrote: > I would specify that "finally" should not raise any exception. > The Invalid_argument exception is not supposed to be raised if the program > was written correctly, right? Wrong :-) If the wrapped function performs I/O, this may lead to unexpected errors. You can surely catch all of them inside the function, but that's not the point. If you have e.g. long-running applications that must not crash, you definitely don't want to be surprised by unexpected exceptions. Therefore, to be safe you'll have to protect your functions. It's also a good habit to prevent yourself from forgetting finalizations. Regards, Markus -- Markus Mottl http://www.oefai.at/~markus ma...@oe... |
From: Brian H. <bh...@sp...> - 2004-06-30 02:52:01
|
On Tue, 29 Jun 2004, Markus Mottl wrote: > On Tue, 29 Jun 2004, Martin Jambon wrote: > > I would specify that "finally" should not raise any exception. > > The Invalid_argument exception is not supposed to be raised if the program > > was written correctly, right? > > Wrong :-) > > If the wrapped function performs I/O, this may lead to unexpected errors. Allocating memory can throw an exception, if you can't allocate any memory. You generally have to allocate memory to call a function. Therefor you can't gaurentee that any function, no matter how trivial, does not throw an exception. The simple act of calling a function could throw an exception. -- "Usenet is like a herd of performing elephants with diarrhea -- massive, difficult to redirect, awe-inspiring, entertaining, and a source of mind-boggling amounts of excrement when you least expect it." - Gene Spafford Brian |
From: Nicolas C. <war...@fr...> - 2004-07-10 15:16:58
|
> > let finally handler f x = > > let r = ( > > try > > f x > > with > > e -> handler(); raise e > > ) in > > handler(); > > r > > And what if the handler raises an exception in the first case? It's often > also more convenient to let the handler take the same argument as the > function. I usually use the following function: As the topic show, you have different kind of way of writing finally : - ignore handler exceptions that's your proposal : case A - specify that handler should not raise any exceptions ok, but what then if it raises one ? * ignore it : we're back to case A * throw it : let's call this case B in the case A, the user cannot see the difference between the handler being correctly called or not, that's why I choose to implement the case B. If the user follows the advice that his handlers should not raise any exceptions then there should be no problem with it. One more precise implementation would be : exception Handler_exc of exn * exn option let finally handler f x = let r = ( try f x with e -> try handler(); raise e with e' -> raise Handler_exc (e',Some e) ) in try handler(); r with e -> raise Handler_exc (e,None) but its a bit complex for this kind of function. Regards, Nicolas Cannasse |
From: Francisco J. V. A. <fv...@ts...> - 2004-06-28 21:13:41
|
Just a remark on parameter order Richard Jones wrote: >On Mon, Jun 28, 2004 at 03:33:56PM +0300, Jere Sanisalo wrote: > > >>Hi, >> >> >One gripe I have about them (and indeed about the List.iter and >List.map functions built into the library) is that the parameter order >is just plain wrong! It's much clearer to write: > >List.iter list (fun elem -> > very long function definition > ...) > >instead of: > >List.iter (fun elem -> > very long function definition > ...) list > >Because what you're iterating over ('list') gets lost in the second >version. > The counter-argument to this is that the library-defined function signature allows for partial evaluation of the fun parameter to iterate (map, fold, etc.) over different lists in the same manner. Anyway, if you want the best of both worlds, become labelled and for the price of two+ chars (tilde-f) distinguish between both invokations: open ListLabels a) iter f:(fun .... -> ...) (has type 'a list -> unit) b) iter list (hast type f: -> unit) Thanks J. Garrigue! Regards, F. Valverde - DTSC, UC3M - Spain |
From: Brian H. <bh...@sp...> - 2004-06-29 03:55:03
|
On Mon, 28 Jun 2004, Richard Jones wrote: > One gripe I have about them (and indeed about the List.iter and > List.map functions built into the library) is that the parameter order > is just plain wrong! It's much clearer to write: Possibly. The argument order picked was delibert to mimic the order of the stanard library, where the function comes first. And the order makes a lot more sense when you consider partial function application. > Why can't we also iterate over floats? I consider it a problem in > OCaml that there is no for ... construct for floats. I don't think extlib can do much to change the basic language. What's the step value? Be careful how you answer- you answer has to work correctly for both: for i = 1e-15 to 1e-14 do ... and for i = 1e300 to 1e301 do ... How do I deal with for i = nan to +inf do ... or for i = -inf to +inf do ... Tail recursive functions are the core looping construct of Ocaml. The purpose of for loops is simply to make certain simple list walking patterns simpler- for anything at all complex, I'd use a tail recursive function instead. The purpose of enumerations is to generalize data structure comprehensions, not to change Ocaml's looping patterns. -- "Usenet is like a herd of performing elephants with diarrhea -- massive, difficult to redirect, awe-inspiring, entertaining, and a source of mind-boggling amounts of excrement when you least expect it." - Gene Spafford Brian |