On 8/28/12 5:36 AM, Luc Maisonobe wrote:
>  
<snip/>
> I thought I had. Perhaps this feature was set up after I gave up
> on this discussion.
>>>> It would be quite easy to change, if it would make your life easier.
>>>>
>>>> The more so that I never saw what is gained from copying the Java hierachy
>>>> (in the particular case of the exceptions): Because some exception inherits
>>>> from the Java standard one does not bring special benefits to the
>>>> application that has to catch that exception. I mean: Is there any piece of
>>>> code that would behave differently if it caught "IllegalArgumentException"
>>>> vs "IllegalStateException"? If not, it could as well be prepared to catch
>>>> a "MathRuntimeException" (and do the same thing).
>>>> [The various exception types are primarily there to discriminate between
>>>> various _problems_; but are not likely helpful to help the caller devise a
>>>> way to react to the exception once it is raised (other than acknowledge the
>>>> fact than CM could not perform the requested action).]
>>>>
>>>> In CM, the vastly overwhelming majority of exceptions are instances of
>>>> "MathIllegalArgumentException" or one of its subclasses.
>>>>
>>>> We have a "NullArgumentException" but we also agreed that it did not have 
>>>> to
>>>> be a subclass of the standard Java "NullPointerException". So in this case,
>>>> we already depart from the "standard". [But we also speculated that the
>>>> policy could to never check for "null" and let the JVM do that, This 
>>>> behaviour
>>>> is _not_ consistent throughout CM.]
>>>>
>>>> Number of occurrences of CM exceptions that are subclasses of those Java
>>>> standard exceptions:
>>>>  * IllegalStateException (43)
>>>>  * UnsupportedOperationException (22)
>>>>  * ArithmeticException (54)
>>>>
>>>> In summary, I have no problem with a "MathRuntimeException" base class 
>>>> which
>>>> "MathIllegalArgumentException", "MathIllegalStateException",
>>>> "MathUnsupportedOperationException", "MathArithmeticException" would 
>>>> inherit
>>>> from.
>>>>
>>>> Applications that call CM would be safe (apart from bugs raising "NPE")
>>>> with a unique catch clause intercepting "MathRuntimeException".
>>> I am happy (and surprised) to read that.
>>> I would really much like to go back to a single root exception
>>> hierarchy. This both helps top level application as depending on context
>>> they can either pinpoint the exception they want to catch or they can
>>> have a grab all strategy. It is their choice.
>> I like throwing (and catching) standard exceptions instead of
>> inventing variants of them, which is why I favored having MathIAE
>> inherit from IAE, etc.  I would have preferred to just throw IAE
>> directly, but we could not agree on how to do that and preserve
>> localization, so we ended up with the current setup where we have
>> custom variants, but they inherit from the standard exceptions.  I
>> am curious, Luc, about exactly what kinds of use cases will really
>> be easier / better for users if we go back to a single-rooted
>> hierarchy.  I get that instead of "catch Exception" or "catch
>> RuntimeException" you can at the top level "catch MathRTE" and that
>> will catch only the exceptions that come (at least originally) from
>> the [math] code.
> Yes, but this is only one aspect.
>
>> Can you help me understand via an example how that
>> is a big benefit that is worth more than being able to "catch IAE"
>> or "catch IOE" directly?
> One of the problems I encounter occurs when building large applications
> with several components layers. At an intermediate level, say just above
> [math], developers know what they are calling and they may decide to
> catch an exception they know about, if they are able to identify it is
> thrown (which is not always obvious). They may also decide the exception
> cannot be handled at their level and simply let it propagate upward. As
> you go upward in the software layers, with different development teams,
> you lose this knowledge and people don't even understand anything about
> mathematics. They can however still catch some large scope exceptions,
> one type per component (say a MathRuntimeException, and a MylibException
> if they know these two sub-components are used). They won't do anything
> with the exception but nicely display them in the graphical user
> interface and stop the application. This works well as long as there is
> one single root per library, but it does not scale with 40 different
> exceptions per libraries.

I think I get your point, but again given transitive / nested
dependencies I would not want to depend on it, even if all of the
components have single-rooted exception hierarchies.  This is
especially true if not all components adhere to the "wrap
everything" rule - i.e., if they can generate and/or propagate RTEs
that do not inherit from their base exception class.  From the
standpoint of the caller, for example, what is the difference
between [math]

0)  throwing IAE
1)  throwing MathIAE derived from IAE
2)  throwing MathIAE derived from MathRTE (base)
assuming that [math] is not signing up to wrap and rethrow every
exception - including IAE - we get from JDK classes?  Will the
caller actually do anything different if the RTE is math-wrapped vs
"naked" but coming out of the [math] code?  I understand that the
try/catch may be several layers removed from the code calling a
[math] API. 

Same applies to NPE, which we don't subclass now, but mostly handle
as IAE.

I guess one thing we might consider is trying to design for the
invariant that we never propagate RTEs without wrapping.  But that
would be a lot of work to retrofit and would have a performance cost.

> Another problem is maintenance. Even if you consider the intermediate
> developer did his work really accurately and managed to identify all
> exceptions thrown by the methods he calls in one version of Apache
> Commons Math. When we change an error detection and decide that a method
> that did throw only MaxCountExceededException a method should throw
> NumberIsToolLargeException instead (or in addition to the existing one),
> then the calling code would still compile, but the new exception would
> now go all the way upward. The two exceptions have no common ancestor
> that can be catched, except Exception itself. With a single rooted
> hierarchy, users can use some defensive programming: they can catch the
> common root and be safe when we change some internal details.
>
> A single root would also bring two things I find useful.
>
> The first useful thing is that the ExceptionContextProvider could be
> implemented at the root level, so we could retrieve this context (in
> fact, I sometime needs even to retrive the pattern and the arguments
> from the context, and we also miss getters for that, but they are easy
> to add). It is not possible to catch ExceptionContextProvider because it
> is not a throwable (Throwable is a class, not an interface, so we
> inherit the Throwable nature from the top level class, not as
> implementing the ExceptionContextProvider interface.
>
> The second useful thing is for [math] development itself. With a single
> root, we can temporarily change its parent class from RuntimeException
> to Exception, then fix all missing throws declaration and javadoc, then
> put the parent class back before committing. This would help having more
> up to date declarations. For now, I am sure we have missed a lot of our
> own exceptions and let them propagate upward without anybody knowing it.
> As a test, I have just changed the parent for
> MathIllegalArgumentException to Exception. I got 1384 compilation
> errors. Just going to the first one (a constructor of
> BaseAbstractUnivariateIntegrator), I saw we did not advertise the fact
> it may throw NumberIsTooSmallException and NotStrictlyPositiveException,
> neither in a throws declaration nor in the javadoc. I did not look at
> the 1383 other errors...

This is a good point.
>
>> What I am missing is how knowing that an
>> aspecific RTE came from within [math] makes a difference.  I am
>> skeptical about ever depending on that kind of conclusion because
>> dependencies may bring [math] code in at multiple levels.  Also, is
>> there an implied assumption in your ideal setup that *no* exceptions
>> propagate to [math] clients other than MRTE (i.e. we catch and wrap
>> everything)?
> No, I don't make this assumption. I consider that at upper levels, code
> can receive exception from all layers underneath ([math] at the very
> bottom, but also other layers in between). With two or three layers, you
> can still handle a few library-wide exceptions (see my example with
> MathRuntimeException, and MylibException above). However, if at one
> level the development rules state that all exception must be caught and
> wrapped (this happens in some critical systems contexts), then a single
> root hierarchy helps a lot.

But if we allow some exceptions to propagate unwrapped, this does
not work, unless I am missing the point here.
>
> My point is that with a single root, we can get the best of two worlds:
> large scope catches and pinpointed catches. The choice remains open for
> users. With a multi-rooted hierarchy, we force users to duplicate the
> same work for all exceptions we may throw, and we also force them to
> recheck everything when we publish a new version, even despite we
> ourselves fail to document these exceptions accurately.

We need to fix the documentation.  If going back to a single root
makes automatic detection of gaps possible, that by itself is almost
enough to get me to agree to go back to the single root.  Your
arguments above (which I honestly only partially follow) are enough
to make me +0 for this change.  I think I probably put too much
weight on favoring standard exceptions when we are really only
talking about "reinventing" a handful of them.

Phil
>
> best regards,
> Luc
>
>> Phil
>>> For sure, this is something that can be done only for a major release.
>>>
>>>>>> Client apps cannot do more with checked exceptions, and can be made as
>>>>>> "safe" by wrapping calls in try-blocks.
>>>>>> On the other hand, client source code is much cleaner without unnecessary
>>>>>> "throws" clauses or wrapping of checked expections at all levels.
>>>>>> Some Java experts go as far as saying that checked exceptions were a
>>>>>> language design mistake (never repeated in languages invented more
>>>>>> recently).
>>>>>>
>>>>>>> There is a reason that NaNs exist.  It is much cheaper to return a
>>>>>>> NaN than to raise (and force the client to handle) an exception. 
>>>>>>> This is not precise and probably can't be made so, but I have always
>>>>>>> looked at things more or less like this:
>>>>>>>
>>>>>>> 0) IAE (which I see no need to specialize as elaborately as we have
>>>>>>> done in [math]) is for clear violation of the documented API
>>>>>>> contract.  The actual parameters "don't make sense" in the context
>>>>>>> of the API.
>>>>>> The "elaboration" is actually very basic (but that's a matter of taste), 
>>>>>> but
>>>>>> it was primarily promoted (by me) in order to hide (as much as possible) 
>>>>>> the
>>>>>> ugliness (another matter of taste) of the "LocalizedFormats" enum, and 
>>>>>> its
>>>>>> inconsequent use (duplication). [Cf. discussions in the archive.]
>>>>>>
>>>>>>> 1) NaN can be returned as the result of a computation which, when
>>>>>>> started with legitimate arguments, does not result in a
>>>>>>> representable value.
>>>>>> According to this description, Sébastien's case _must_ be handled by an
>>>>>> exception: the argument is _not_ legtimate.
>>>>>> The usage of NaN I was referring to is to let a computation proceed 
>>>>>> ("follow
>>>>>> an unexceptional path") in the hope that the final result might still be
>>>>>> meaningful.
>>>>>> If the NaN persists, not checking for it and signalling the problem (i.e.
>>>>>> raise an exception) is a bug. This is to avoid that (and be robust) that 
>>>>>> we
>>>>>> do extensive precondition checks in CM. But this has the unavoidable
>>>>>> drawback that the use of NaN as suggested is much less likely to be 
>>>>>> feasible
>>>>>> when calling CM code. Once realizing that, it becomes much less obvious 
>>>>>> that
>>>>>> there is _any_ advantage of letting NaNs propagate...
>>>>>> [Anyone has an example of NaN usage? Please let me know.]
>>>>> I use NaN a lot as an indicator that a variable has not been fully
>>>>> initialized yet. This occurs for example in iterative algorithms, where
>>>>> some result is computed deep inside some loop and we don't know when the
>>>>> loop will end. Then I write something along these lines:
>>>>>
>>>>>   while (Double.isNaN(result)) {
>>>>>      // do something that hopefully will change result to non-NaN
>>>>>   }
>>>>>
>>>>>   // now I know result has been computed
>>>>>
>>>>> Another use is to initialize some fields in class to values I know are
>>>>> not meaningful. I can then us NaN as a marker to do lazy evaluation for
>>>>> values that takes time to compute and should be computed only when both
>>>>> really needed and when everything required for their computation is
>>>>> available.
>>>> I should have said "[...] example of NaN usage, beyond singling out
>>>> unitialized data [...]". The above makes use of NaN as "invalid" because it
>>>> is not initialized (yet).
>>> Yes.
>>>
>>>> I'd assume that if "result" stays NaN after the allowed number of
>>>> iterations, you raise an exception, i.e. you don't propagate NaN as the
>>>> output of a computation that cannot provide a useful result. However, this
>>>> (propagating NaN) is the behaviour of "srqt(-1)", for example.
>>>> Thus, if you raise an exception, your computation does not behave in the
>>>> same way as the function "sqrt".
>>>>
>>>>> Another use is simply to detect some special cases in computations (like
>>>>> sqrt(-1) or 0/0). I do the computation first and check the NaN
>>>>> afterwards. See for example the detection of NaNs in the linear
>>>>> combinations in MathArrays or in the nth order Brent solver.
>>>> OK, this is a good example, in line with the intended usage of NaN (as it
>>>> avoids inserting control structures in the computation).
>>> Yes. One of the main use case for this is when a computation involves a
>>> loop and failure is very rare. So we avoid costly numerous if statements
>>> within the loop and do a single check. In the few cases this single
>>> check fails, we go to a diffrent branch to handle the failure. This is
>>> exactly what is done in linear combination.
>>>
>>>>> Another use of NaNs occurs when integrating various code components from
>>>>> different origins in a single application. Data is forwarded between the
>>>>> various components in all directions. Components never share the same
>>>>> exceptions mechanisms. Components either process NaNs specially (which
>>>>> is good) or they let the processor propagate them (it is what the IEEE
>>>>> standard mandates) and at the end you can detect it reliably at
>>>>> application level.
>>>> I'm not sure I understand this. Is it good or bad that a component lets 
>>>> NaNs
>>>> propagate? Are there situations when it's good and others where it's bad?
>>> In the cases I encountered, it is always good to have NaNs propagated. A
>>> component that is not an application by itself but only a part (low or
>>> intermediate level) often cannot decide at its level how to handle NaNs
>>> except in rare cases. So it propagates them upward. The previous example
>>> (linear combination in [math]) is of course a counter-example: we are at
>>> low level, we know how to handle the NaN for this operation, so we
>>> detect it and fix it.
>>>
>>>> That's why I was asking (cf. quote from previous post below) what are the
>>>> criteria, so that contributors know how to write code when the feature 
>>>> falls
>>>> in one or the other category.
>>>>
>>>>>>> The problem is that contracts can often be written so that instances
>>>>>>> of 1) are turned into instances of 0).  Gamma(-) is a great
>>>>>>> example.  The singularities at negative integers could be viewed as
>>>>>>> making negative integer arguments "illegal" or "nonsense" from the
>>>>>>> API standpoint,
>>>>>> They are just nonsense (not just from an API standpoint).
>>>>>>
>>>>>>> or legitimate arguments for which no well-defined,
>>>>>>> representable value can be returned.  Personally, I would prefer to
>>>>>>> get NaN back from this function and just point out the existence of
>>>>>>> the singularities in the javadoc.
>>>>>> This is consistent with how basic math functions behave, but not with the
>>>>>> general rule/convention of most of CM code.
>>>>>> It may be fine that we have several ways to deal with exceptional
>>>>>> conditions, but it might be nice, as with formatting, to have rules so 
>>>>>> that
>>>>>> we know how to write contributions.
>>>>> Too many rules are not a solution, especially when there are no tools to
>>>>> help enforce these rules are obeyed. Relying only on the fact human
>>>>> proof-reading will enforce them is wishful thinking.
>>>>>
>>>> What is "too many"? ["How long should a person's legs be?" ;-)]
>>>> I don't agree with the "wishful thinking" statement; a "diff" could 
>>>> probably
>>>> show a lot a manual corrections to the code and comment formatting. [Mainly
>>>> in the sources which I touched at some point...]
>>> I'm not sure I understand your point. Mine is that rules that are not
>>> backed by automated tools are a pain to enforce, and hence are not
>>> fulfilled most of the time, except at a tremendous human resource cost.
>>> In fact, even rules which can be associated with tools are broken during
>>> development for some time. We do not use
>>> checkstyle/CLIRR/findbugs/PMD/RAT for all commits for example, but do a
>>> fix pass from time to time.
>>>
>>>> There are other areas where there is only human control, namely the "svn
>>>> log" messages where (no less picky) rules are enforced just because it
>>>> helps _humans_ in their change overview task.
>>>>
>>>> As pointed out by Jared, it's not a big problem to comply with rules once
>>>> you know them.
>>> I fully agree with that, but I also think Phil is right when he says too
>>> many rules may discourage potential contributors. I remember a link he
>>> sent to us months ago about to a presentation by Michael Meeks about
>>> interacting with new developers
>>> <http://people.gnome.org/~michael/data/2011-10-13-new-developers.pdf>.
>>> Slides numers 3 an 4 are a fabulous example. I think we are lucky Jared
>>> has this state of mind and accepts picky rules easily. I'm not sure such
>>> an open mind is widespread among potential contributors.
>>>
>>>> Keeping source code tidy is quite helpful, and potential contributors will
>>>> be happy that they can read any CM source files and immediately recognize
>>>> that they are part of the same library...
>>> Yes, of course. But the entry barrier should not be too high.
>>>
>>> best regards,
>>> Luc
>>>
>>>> Best regards,
>>>> Gilles
>>>>
>>>> ---------------------------------------------------------------------
>>>> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
>>>> For additional commands, e-mail: dev-h...@commons.apache.org
>>>>
>>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
>>> For additional commands, e-mail: dev-h...@commons.apache.org
>>>
>>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
>> For additional commands, e-mail: dev-h...@commons.apache.org
>>
>>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
> For additional commands, e-mail: dev-h...@commons.apache.org
>
>



---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@commons.apache.org
For additional commands, e-mail: dev-h...@commons.apache.org

Reply via email to