
On Tue, Mar 24, 2009 at 1:27 PM, Xiao-Yong Jin
Henning Thielemann
writes: Try to never use exception handling for catching programming errors! Division by zero is undefined, thus a programming error when it occurs. http://www.haskell.org/haskellwiki/Error http://www.haskell.org/haskellwiki/Exception I'm afraid, a Maybe or Either or Exceptional (see explicit-exception package) return value is the only way to handle exceptional return values properly. Maybe in the larger context of your problem zero denominators can be avoided? Then go this way.
Using div is just an example I'm testing with what I read in the book Real World Haskell. The real thing I'm trying to do is inverting a matrix. Say, I want to write
invMat :: Matrix -> Matrix
You won't be able to invert all the matrix, mathematically. And computationally, even a larger set of matrix might fail to be inverted because of the finite precision. It is relatively easier and more efficient to spot such a problem within this 'invMat' function. Because testing the singularity of a matrix is equally hard as invert it. So all I can do when 'invMat' spot a singular matrix are
a) Return Either/Maybe to signal an error. b) Wrap it in a monad. c) Define a dynamic exception and throw it.
In general if a function is partial we can either make it total by extending its range or restricting its domain. Also we can signal it using runtime or compile-time mechanisms. Options a & b are equivalent (i.e. extend the range, compile-time notification) and option c is also another way of extending the range, but using runtime notification. If we try the other approach, we need to express the totality of invMat by restricting its domain, so we can add, for example, a phantom type to Matrix to signal it is invertible. As you need to construct the Matrix before trying to invert it you can always make the constructors smart enough to bundle the Matrix with such properties. Of course there's need to do some runtime verifications earlier, but the clients of invMat are required to do the verification earlier or pass it to their clients (up to the level that can handle with this issue): data Invertible tryInvertible :: Matrix a -> Maybe (Matrix Invertible) invMat :: Matrix Invertible -> Matrix Invertible You could use different forms of evidence (e.g. phantom types, type classes) but the idea is the same.
The problem is that there will be many functions using such a function to invert a matrix, making this inversion function return Either/Maybe or packing it in a monad is just a big headache. It is impractical to use method (a), because not every function that uses 'invMat' knows how to deal with 'invMat' not giving an answer. So we need to use method (b), to use monad to parse our matrix around.
invMat :: Matrix -> NumericCancerMonad Matrix
It hides the exceptional nature of numerical computations very well, but it is cancer in the code. Whenever any function wants to use invMat, it is mutated. This is just madness. You don't want to make all the code to be monadic just because of singularities in numeric calculation. Therefore, in my opinion, method (c) is my only option. And because I don't always want to deal with such problem in the IO monad, I create this beast 'unsafePerformIO . try . evaluate' to convert some potential disastrous result to 'Either Disaster Result'.
You might argue that Haskell actually deals with such numerical problem with 'NaN', 'Infinite'. But, some numerical operations behave weird with these special values, and using 'isNan', 'isInfinite' alike is just another big mess. They are going to be all over the place and not actually better than 'case ... of' and 'fromMaybe'.
I can't really think of another option for this kind of situation, apart from letting my code to be infected by NumericCancerMonad.
If anyone on this list has some thoughts on this matter, please share them. Many thanks. -- c/* __o/* <\ * (__ */\ <
Best regards, Daniel Yokomizo