Thanks Andreas for your different point of view. So if i want these exceptions for my apps without breaking current behaviour for yours, then i need a default handler that quietly pass the exception and proceed with NaN or Inf. I do not figure yet how it would be implemented but that's something interesting to look at. Nicolas Andreas Raab: nicolas cellier wrote:
|
In reply to this post by stéphane ducasse-2
Markus, of course, i agree that defensive programming is a must at least in critical places, and no i do not like the idea to have my data corrupted. In fact, defensive programming at low level is a prerequisite for doing optimistic programming at higher level.
Once you have put these exceptions at critical low levels, it is in some cases (i do not say all cases) far more comfortable to squeeze higher level precondition/postcondition tests and use exception handling instead. This is where i am speaking of programming style. Lost of performance come not only from low level defense, but also from testing several times the same assertion in the call chain. Removing second half is not risky. Nicolas Markus Gaelli:
|
On Mar 17, 2006, at 3:49 PM, [hidden email] wrote: > Once you have put these exceptions at critical low levels, it is in > some cases (i do not say all cases) far more comfortable to squeeze > higher level precondition/postcondition tests and use exception > handling instead. This is where i am speaking of programming style. > > Lost of performance come not only from low level defense, but also > from testing several times the same assertion in the call chain. > Removing second half is not risky. Ah...ok. Sure, I don't like checking the same stuff over and over again. To quote myself from earlier in this thread: > > Example: I have a method to solve the quadratic equation f(x)=axx+bx+c > > Array >> #solveQuadraticEquation > |a b c discriminant solutions | > self precondition: [self size = 3 and: [self allSatisfy: [:each | > each isNumber]]]. > "Do I have to state here that the discriminant should be >= 0? I > do not think so." > a:= self first. > b:= self second. > c:= self third. > discriminant:=(b*b)-(4*a*c). > solutions := Set new. > solutions > add:((0-b+discriminant sqrt)/(2*a)); > add:((0-b-discriminant sqrt)/(2*a)). > ^solutions > > If the discriminant<0 the precondition of sqrt should fail. I do > not want to state that here also as I am lazy and as I have not > computed the discriminant in the beginning. > Meanwhile I came to the conclusion that a better style for above method would certainly be to always return a collection of solutions, in the case of a negative discriminant the collection should just be empty. So it better looked like: Array >> #solveQuadraticEquation |a b c discriminant solutions | self precondition: [self size = 3 and: [self allSatisfy: [:each | each isNumber]]]. a:= self first. b:= self second. c:= self third. discriminant:=(b*b)-(4*a*c). discriminant < 0 ifTrue: [^#()]. solutions := Set new. solutions add:((0-b+discriminant sqrt)/(2*a)); add:((0-b-discriminant sqrt)/(2*a)). self postcondition: [ (solutions size between: 1 and: 2) and: [ solutions allSatisfy: [:each | ((a*each*each)+(b*each)+c) abs <= 1.0e-10]]]. ^solutions A typical case for a predictable situation which can be handled perfectly well without having to deal with any exceptions. I still fail to see why I would have to introduce exception handling at a higher level due to not stating the same assertion over and over again: I did not understand your NaN example, could you elaborate on that or send another example? Note that I added a post condition, so here come a PRICE QUESTION: (the person answering to this one last gets a beer (or bounty?) next time we meet personally... ;-) Would test cases for that method still need to state the expected outcome? Sth. along the line of ArrayTest >> testSolveQuadaticEquation self assert: (#(1 -4 4) solveQuadraticEquation asArray = #(2.0)) (...) Or would it be sufficient to just provide examples a la: ArrayTest >> testSolveQuadaticEquation #(1 -4 4) solveQuadraticEquation. (...) as the postcondition here is already basically an inverse function, and "nothing" should go wrong.... Any opinions on that? Cheers, Markus |
In reply to this post by stéphane ducasse-2
Well, your example would fail with
#(1.0e300 5.0e300 6.0e300) solveQuadraticEquation because of NaNs, but that would be detected in the post conditions, though solution is obviously #(-3 -2). And your postcondition might fail with #(3.0e100 5.0e100 2.0e100) solveQuadraticEquation because residual error might well get bigger than the 1.0e-10 tolerance, though accuracy on solutions would be quite good. (sorry i have not a running image to assert what i say, but you will soon find similar example). In the above case, you could catch this NaN and maybe retry with another algorithm that would first normalize first or last coefficient of you polynomial. The example is not very good because we cannot figure why we need two algorithms here. But think of solving an eigenvalue problem or a Riccati equation, then we have several possible algorithms an could retry with matrix balancing or with another algorithm before giving up with an error notification. Nicolas Markus Gaelli:
|
In reply to this post by stéphane ducasse-2
Ah, i have forgotten another thing: your algorithm would almost work with Complex, except you should remove the discriminant < 0 test and change the precondition...
There, you can introduce exception for handling -2 sqrt, with two possibilities: - return with empty collection - return with complex solutions This is just one possible style of programming (i would not say the best). I think it would be better with a handler block as extra argument in this simple case. Nicolas Markus Gaelli:
|
In reply to this post by stéphane ducasse-2
What a fool am i, your example is already an eigenvalue problem : you are searching the eigenvalues of [0 , 1 ; -c/a , -b/a]. So the alternative algorithm when catching a Nan could be to ask lapack to solve that for you, for example with a Schur decomposition. Nicolas [hidden email]:
|
In reply to this post by Nicolas Cellier-3
On Mar 17, 2006, at 5:19 PM, [hidden email] wrote: Well, your example would fail withIndeed. I get: a Set(NaN NaN)
I should strengthen my precondition. I declare herewith that the whole thing only works Numbers but Floats Who understands Floats (yeah, I know there is that FAQ, that I should frequently ask...) anyhow...;-) Ok, thanks a lot for the nice counter examples above! :-) Guess it boils down to the question if I want to compute something first, at least partially, only to find out, if "the whole thing" is computable like that at all. If it is computable, I might have to repeat part of that computation. Using exception handling can avoid this duplication. To check for an expected exception being thrown, I need to create a subclass in the exception hierarchy. So it is a tradeoff between code duplication + performance hits between some "canCompute"/ "doCompute" combos on the one side - and the introduction of some new, quite empty, exception classes and exception handling code on the other side. Right? Rephrasing it with some metaphor: In the optimistic way of developing you propose, you take a key that might fit into a door and try it hard, you will know at the end if it was ok and you are in. Otherwise you can remove the broken parts of the key (having pushed it too hard, parts of it got stuck in the lock...) with that special "remove my broken key out of that lock" exception-catching-tool you already have created for your toolbox, and then try the next one. The pessimist would iterate over all keys and find the one that unlocks the door, take it out again(!) and then use it, being sure that it is the right one. Thanks again for your patience, and feel invited for a beer ;-) Markus Nicolas |
Free forum by Nabble | Edit this page |