-
Notifications
You must be signed in to change notification settings - Fork 178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handling and propagating errors in stdlib #224
Comments
Would the idea still be that if the I think your approach can work. The only possible downside that I can see is that you can notice that built-in Fortran functions never return a derived type, but only simple arguments such as integers or arrays. The reason for that is that it does not impose any particular derived type on the user. In here, we would be imposing the type Regarding speed, the derived type can possibly be a bit slower than an integer. So maybe not a good idea for small functions in hot loops. But for IO functions, the possible overhead is probably small. Just like integer, And the Pros are: extensible, can convey more rich error information such as a string. And it is still relatively simple. If we chose to go with returning just an integer, then we can document the various different errors as different integers, and then provide a function that prints a nice error message based on the integer number. So I think the key difference to an integer is that So it seems to me if just a simple error with no extra information is needed, the integer might be better. But if more information must be attached to the error that is only known at runtime, then I think your |
Actually, originally I wanted to propose to make them mandatory. But to be in accordance with Fortrans usual way of doing things, we could make it optional, and hope, that people would always pass (and handle it) in order to obtain a robust program. I agree, that Fortran ususally operates with scalar types and arrays of them preventing to impose derived types on the programmer. But in my opinion, this is exaclty what makes many things quite painful, needing a lot of extra-coding for basic stuff. As for
|
Why would you consider a program not robust if it does not handle the error? If the status is not provided, then the function stops the program with a useful error message. I consider this very robust and the desired behavior. If I don't want to handle the error, I want to get a nice error message and a stacktrace. If the user wants to handle the error, then the status is provided. So it seems it is robust either way. Regarding the stacktrace --- I think it's the compiler's job to create a nice stacktrace when the error occurs. GFortran does it, although I would like it to be formatted better. What we can do is to try using |
I overall like it, although I don't think it's feasible to use for everything. For procedures that return a single result we use functions, and if we use an So, without being convinced otherwise, my suggestion would be to limit such status/error propagation to subroutines that modify variables in-place. I/O and file system stuff is I think the perfect candidate for such things, stats functions not so much. |
@milancurcic I agree. The stat functions will be used in hot loops, so we do not want to have the overhead of more general error handling there. The IO functions will have the overhead of the IO, so we can afford more general error handling there. |
Of course, this error handling mechanism is not meant for functions at all. Especially numerical functions have better ways of signalizing errors by returning a specific value (e.g. nan). I'd propose to use this error-handling mechanism in those cases, where otherwise an |
There are 2 schools of thought I think on having an However, if such an error occurs, it is more likely a bug in the program, the procedure was called with invalid inputs, and that's on the caller. You need to fix your program. So you could take the stance that, here's the requirements of calling this procedure, if you violate them that's on you, or, you can strive for "total functions", that can gracefully handle all possible inputs, but force the caller to deal with potential errors. I generally like using libraries that strive towards the latter, as it makes explicit in the interface that things might go wrong, but there are of course performance penalties, and there's nothing wrong with a procedure taking the stance of "if you call me with bad inputs, I crash". |
I consider a procedure that only calls ERROR STOP when it encounters a problem to be a nightmare, but I also consider one that passes a status flag up to a calling procedure, that represents a problem a calling procedure can't be expected to handle, to also be a nightmare. Instead, a procedure, if it encounters a problem it thinks is unhandleable, should print a detailed error message describing the type of problem, and the location where it was found, and then call ERROR STOP. On rare occasions when the errors are genuinely handleable (e.g. a fast but non-robust method, that discovers it is having problems may pass a flag indicating that a more robust method is needed), it should pass an optional STATUS flag, that, if not present, stops the application with the detailed error message. FWIW I also consider most user errors to be unhadleable. See Herb Sutter’s paper for SC22/WG21, "Zero-overhead deterministic exceptions: Throwing values”, for a discussion of the problems with trying to handle user or allocation errors. |
Yes, I think we all agree to print detailed error information before calling error stop.
…On Thu, Jul 23, 2020, at 10:00 PM, William B. Clodius wrote:
I consider a procedure that only calls ERROR STOP when it encounters a
problem to be a nightmare, but I also consider one that passes a status
flag up to a calling procedure, that represents a problem a calling
procedure can't be expected to handle, to also be a nightmare. Instead,
a procedure, if it encounters a problem it thinks is unhandleable,
should print a detailed error message describing the type of problem,
and the location where it was found, and then call ERROR STOP. On rare
occasions when the errors are genuinely handleable (e.g. a fast but
non-robust method, that discovers it is having problems may pass a flag
indicating that a more robust method is needed), it should pass an
optional STATUS flag, that, if not present, stops the application with
the detailed error message. FWIW I also consider most user errors to be
unhadleable. See Herb Sutter’s paper for SC22/WG21, "Zero-overhead
deterministic exceptions: Throwing values”,
<http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf>
for a discussion of the problems with trying to handle user or
allocation errors.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#224 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAFAWHYE3IFPQESMTZGE33R5EBOBANCNFSM4PFPIXJQ>.
|
Indeed, I think we agree all on that. However, this means that functions are not allowed to be |
Of course, As for functions: I think, we should only allow for functions which either never fail or can report their failure via a special value of their return type (e.g. |
I am not sure I fully agree with that. For example, for the function
I don't think that returning |
It seems This function indeed does not compile: pure integer function f(x)
integer, intent(in) :: x
f = x+1
stop "ok"
end function with:
But this one compiles: pure integer function f(x)
integer, intent(in) :: x
f = x+1
error stop "ok"
end function |
According to MRC: "Execution of an |
Since Fortran 2018, indeed. |
There you go. So this is an option. One still cannot have any pure integer function f(x)
integer, intent(in) :: x
character(len=100) :: msg
msg = "some text"
f = x+1
error stop msg
end function |
This is indeed an option. However it might limit the number of compilers that support pure procedures with F18 |
@aradi the main performance impact I see in your proposal is that I believe the finalizer will be called every time a procedure is called with a If the class(error) can be used to pass arbitrary information the structure of a STAT object can be arbitrarily complex which can make constructing the stack and accessing the object from the stack more costly. I don't know what effect that will have on the finalizer. There is a cost for an arbitrary class hierarchy. FWIW I have minor objections to the name |
Don't forget about those of us developing GUI applications. For us, an error_stop is never acceptable, and printing a message to the console is useless since the user may not even see that. All errors have to be handled. I like the |
I agree, the finalization may give an extra overhead, especially due to the
I have rewritten FX-Error to demonstrate this way of error handling, so have a look at the relevant source files there for further details. |
@wclodius2 As for the naming convention, I've opened issue #225 on derived type naming conventions. |
@jvdp1 Yes, returning The error-reporting mechanism here is more for cases, where the error is expected by design as the success of the call can not be warranted by the programmer/caller, like
|
@aradi your test program, |
As near as I can tell the most user friendly way to propagate errors
I will put off discussing the form of error reporting and focus on There are two categories of information that a
The severity flags seem to be more useful for logging routines The most obvious type for The other option is a derived type. Such a derived type can
An integer component has many of the advantages and disadvantages of Using other intrinsic types as component to distinguish different Using the class hierarchy to distinguish different errors has one The above analysis prompts the following questions: Should the library support If it supports If it supports A. Integers? B. A derived type wrapper around integers with the component public? C. A derived type wrapper around integers with the component private? D. A derived type that uses the class hierarchy to distinguish errors? If the library uses a derived type to support If the library uses a derived type to support If the library supports FWIW my answers to the above questions are that the library should |
I pretty much agree with everything that William wrote and have the same preferences (slight preference for an integer, but not opposed to a derived type).
…On Wed, Jul 29, 2020, at 5:15 PM, William B. Clodius wrote:
As near as I can tell the most user friendly way to propagate errors
in Fortran is through an optional argument, typically called `STAT` or
`STATUS`. The programmer ensures that if `STAT` is present the
resulting argument has information about the nature of any error
encountered in executing the subprogram, and if `STAT` is absent the
program stops if an error is found and reports details about the
nature of the error to either `ERROR_UNIT` or an appropriate log
file. The main questions to be answered are:
* What information should `STAT` convey;
* What type should `STAT` have; and
* What form should the error reporting take?
I will put off discussing the form of error reporting and focus on
the information `STAT` could convey, and options for the type of
`STAT`.
There are two categories of information that a `STAT` argument might
convey: the severity of the error, and the cause of the error.
FLIBS <http://flibs.sourceforge.net/m_exception.html> and
Futility <https://github.com/CASL/Futility/blob/master/src/ExceptionHandler.f90>
seem to focus on the severity of the error providing values
representing:
* Information - provides information to the user without stopping;
* Warning - provides a warning to the user without stopping;
* Error - that will stop processing only if stop on error is set;
* Fatal Error - that will always stop processing; and
* Failure - not well defined
The severity flags seem to be more useful for logging routines
and not for use as a `STAT` argument. My cursory review indicates that
the FLIBS and Futility library codes focus on error reporting and not
on error handling. As to the cause of the error, in many cases a
library code is well focussed and can have only one cause of
errors. For such codes a simple success/failure flag is sufficient to
identify the cause. But for some codes there can be multiple causes of
failure, e.g., a code that reads a value out of a file may fail
because the file does not exist, the file exists, but does not have
read privileges, an end of file was found before the desired value,
the value did not have the desired form, or the value was outside its
expected range. Each cause has a different possible fix, and each
cause should be distinguished by the `STAT` argument value. They can
be distinguished by either an ad hoc module specific set of error
codes, or by a library defined set of error codes.
The most obvious type for `STAT` is a default `INTEGER`. This is what
the Fortran standard has historically used, and, as a result, what
every Fortran programmer is familiar with. The use of an integer has a
bad rap as the standard did nothing for decades about standardizing
the values, so that all a user could rely on was that a non-zero value
indicated that an error had occurred. Even when it standardized the
meaning of some values in `ISO_FORTRAN_ENV` it limited the number of
defined values to a handful. However integers could be used for
either an application specific enumeration of error codes, or for a
library wide enumeration of error code values. This allows it to
potentially convey a significant amount of information about the
nature of an error. It has the advantage that no wrapper procedures
are needed for comparisons or setting values, so it can be very
efficient. A problem with an application specific enumeration is that
different applications may use the same numeric value for different
errors, or different numeric values for essentially the same error. A
problem with a library wide enumeration is that it will initially be
incomplete, requiring updates. Another problem is that it will be
large, I suspect with more than a hundred values, so finding the
appropriate code will require a detailed search. FWIW I have created
my own enumeration of error codes. It numbers over 90 entries, though
some are, I believe, relatively useless so trimming might lower the
number of codes to about 80.
The other option is a derived type. Such a derived type can
distinguish between different errors in several ways:
1. By a component that is an integer that takes on different values
for different categories of errors; or
2. By a component that is another intrinsic type that takes on
different values for different errors; or
3. By having the derived type be a class and using inheritance to
create different classes that represent different errors.
An integer component has many of the advantages and disadvantages of
using an integer directly. Unless a library wide enumeration of values
is provided, different applications may use the same value to
represent different sources of errors or different values to
represent the same error. If the component is not made private, then
comparisons and setting values can be very efficient, at the syntactic
cost of consistently referring to the component. If the component is
made private then comparisons and setting values will require the
(implicit) use of simple procedures, that will require inter-module
optimization to reduce the cost.
Using other intrinsic types as component to distinguish different
errors is problematic. I cannot see enumerating errors using a `REAL`
or `COMPLEX`, though it can be done. A `LOGICAL` value can be used to
note whether an error is active, but not to meaningfully distinguish
errors. A `CHARACTER` string can be used to distinguish errors, but
the comparisons would have a significant performance cost. Still a
`CHARACTER` string component can be used to supplement an `INTEGER`
component, by providing an error specific message. The usefulness
of this message depends on whether information about the error should
be specific to where it is first detected, or to where it is
reported. In the first case the string is useful, in the second case
it is much less useful.
Using the class hierarchy to distinguish different errors has one
major advantage: the processor will ensure that different names of
errors will correspond to different values. However the same name can
be used by different applications, and if defined in different modules
the same name can correspond to different values. This problem again
might be addressed by a library wide enumeration of error
codes. However from @aradi <https://github.com/aradi>'s code it seems
that `ALLOCATE` needs to be
used to activate an error and `MOVE_ALLOC` needs to be used to
associate that specific error with the nominal `STAT` argument, though
maybe `ALLOCATE( TYPE(error-type-name):: STAT ) might work instead. In
any case the syntax would be unintuitive to a novice.
The above analysis prompts the following questions:
Should the library support `STAT` arguments? (yes/no)
If it supports `STAT` arguments should they represent severity or
cause?
If it supports `STAT` arguments should they be:
A. Integers?
B. A derived type wrapper around integers with the component public?
C. A derived type wrapper around integers with the component private?
D. A derived type that uses the class hierarchy to distinguish errors?
If the library uses a derived type to support `STAT` arguments, should
it have a `CHARACTER` string component, e.g., `MSG`?
If the library uses a derived type to support `STAT` arguments, should
it follow the integer path of using a special value to indicate that
no error was found or should there be an internal logical(?) flag to
indicate whether an error occurred?
If the library supports `STAT` arguments, should it provide a
predefined enumeration of error codes, or should it leave it up to the
module's programmer to come up with an ad hoc enumeration of module
specific codes?
FWIW my answers to the above questions are that the library should
support `STAT` arguments, that they should represent the cause of the
error, that they should be either integers or a derived type wrapper
around integers with the component public, with a mild preference for
simple integers, that the derived type, if it is used, should have a
string component, that it should use a special value to indicate that
no error occurred, and that it should provide a pre-defined
enumeration of error codes.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#224 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAFAWHYBAVPYKCH4OJNH23R6CUR7ANCNFSM4PFPIXJQ>.
|
@wclodius2 The finalizer in my approach made sure, that one can not let the error being unhandled: If the subroutine returns an error, it must be handled (and explicitely deactivated) before it goes out of scope. Especially, anti-patterns like
would give then a run-time error. If that is too much of constraints, we could leave away the finalizer. Then, we were basically back to my original proposal using a derived type as status container. No allocation, no I would still argue for using derived types / classes for error classification, as this is the only way can stay flexible with the payload of the error. Some (maybe most) errors only return a simple error message, but sometimes also additiional data could be useful to understand the details of the error (numerical value of the determinant of the matrix, which could not be inverted...). We can make the error msg + integer combination the default case, but it should be possible to go beyond that, if needed.
Errors with more complicated payload could be derived from |
@aradi it is not clear to me that being flexible with the payload of an error is that useful. If you need detailed context for handling the error then you are better off handling it where it is first detected and you have all the context available. What errors do you see needing large payloads?
FWIW passing unbounded objects on the stack is one of Herb Sutters' complaints about the C++ exception handling system: "Zero-overhead deterministic exceptions: Throwing values”, http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf <http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r0.pdf> . According to his claims it has a significant run-time cost, and little benefit.
… On Jul 30, 2020, at 8:28 AM, Bálint Aradi ***@***.***> wrote:
@wclodius2 <https://github.com/wclodius2> The finalizer in my approach made sure, that one can not let the error being unhandled: If the subroutine returns an error, it must be handled (and explicitely deactivated) before it goes out of scope. Especially, anti-patterns like
! We pass the optional error argument, but do not hande it afterwards before passing it to the next routine.
call some_routine(error=error, ...)
call other_routine(error=error,...)
...
would give then a run-time error. If that is too much of constraints, we could leave away the finalizer. Then, we were basically back to my original proposal using a derived type as status container. No allocation, no move_alloc necessary then.
I would still argue for using derived types / classes for error classification, as this is the only way can stay flexible with the payload of the error. Some (maybe most) errors only return a simple error message, but sometimes also additiional data could be useful to understand the details of the error (numerical value of the determinant of the matrix, which could not be inverted...). We can make the error msg + integer combination the default case, but it should be possible to go beyond that, if needed.
The following minimal working example demonstrates what I vaguealy have in mind:
module error_handling
implicit none
private
public :: general_error, status, raise_error
type :: general_error
character(:), allocatable :: msg
integer :: error_code
contains
procedure :: as_char => general_error_as_char
end type general_error
type :: status
class(general_error), allocatable :: error
contains
procedure :: is_ok => status_is_ok
procedure :: has_failed => status_has_failed
end type status
interface general_error
module procedure construct_general_error
end interface general_error
contains
pure function status_is_ok(this) result(isok)
class(status), intent(in) :: this
logical :: isok
isok = .not. allocated(this%error)
end function status_is_ok
pure function status_has_failed(this) result(hasfailed)
class(status), intent(in) :: this
logical :: hasfailed
hasfailed = allocated(this%error)
end function status_has_failed
function construct_general_error(msg, errorcode) result(this)
character(:), allocatable :: msg
integer, intent(in) :: errorcode
type(general_error) :: this
this%msg = msg
this%error_code = errorcode
end function construct_general_error
function general_error_as_char(this) result(errorchar)
class(general_error), intent(in) :: this
character(:), allocatable :: errorchar
character(100) :: buffer
write(buffer, "(' (Error code: ',I0,')')") this%error_code
errorchar = this%msg // trim(buffer)
end function general_error_as_char
subroutine raise_error(stat, error)
type(status), optional, intent(out) :: stat
class(general_error), intent(in) :: error
character(:), allocatable :: errormsg
if (present(stat)) then
stat%error = error
else
errormsg = error%as_char()
error stop errormsg
end if
end subroutine raise_error
end module error_handling
program test_error_handling
use error_handling
type(status) :: stat
call error_raiser(stat, .true.)
if (stat%has_failed()) then
print *, "Error occured: ", stat%error%as_char()
end if
contains
subroutine error_raiser(stat, raise)
type(status), optional, intent(out) :: stat
logical, intent(in) :: raise
if (raise) then
! It either sets an error in stat, or stops if stat was not present
call raise_error(stat, general_error("Some error occured", -1))
return
end if
end subroutine error_raiser
end program test_error_handling
Errors with more complicated payload could be derived from type(general_error). If the handler wished to access the additional details, it would have to use select type to access the content of the derived error type.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub <#224 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/APTQDOTNCURVZUJFVSOTMD3R6F7SJANCNFSM4PFPIXJQ>.
|
I agree that the error handling of Fortran is quite cumbersome. I would prefer if basic building blocks of the stdlib would use
This means extra work for the user, but all intrinsic statements (i.e. If we would enforce error propagation by means of an error type, we would essentially forbid the use of pure functions! |
Fortran 2018 increased the functionality of ERROR STOP by:
1. Allowing it in pure procedures
2. Allowing it to have runtime stop codes.
However the num her of processors "in the wild” that don’t implement this part of F18 is still very large, and it is probably best that the standard library limit itself to F08 functionality. In F08 runtime stop codes can sometimes be kluged, by using SELECT CASE to choose at runtime among a fixed set of static stop codes.
Constraint C1583 in my copy of the draft of the standard for PURE functions requires that all non-pointer arguments of the function have the INTENT(IN) or VALUE attribute.
Even without the constraint Fortran imposes only a partial order of evaluation for many expression, so functions with side effects, such as INTENT(INOUT) or INTENT(OUT) are generally a bad idea unless you can guarantee they will only be used with great discipline.
Pure functions can indicate a problem was found either by returning a special value, e.g. NaN for REAL function results, or -1 for some INTEGER function results, or by invoking ERROR STOP in F18.
FWIW I have taken to using the idiom for error handling roughly equivalent to
if (.not. isdir(the_dir) ) then
if ( present(stat) ) then
stat = missing_dir_failure
return
else
call error_handler( the_dir // ‘ is missing’, &
module = this_module, &
procedure = this_procedure, &
stat = missing_dir_failure ) ! invoking error stop
end if
else
opent(newlun=lun, file=the_dir // filename, … )
end if
Note that following FLIBS and FUTIL I am considering adding a `LEVEL` keyword to `ERROR_HANDLER` which can take as arguments predefined values: `INFORMATION`, `WARNING`, `ERROR`, or `FATAL_ERROR`.
|
If the conditions that lead to an error stop are "identifiable" from the
outside, like the above example, then we can always allow for a wrapper
that checks the error conditions first. Such a wrapper can then be used to
pass on the information that something went wrong and allow for error
recovery. Whether a programmer wants to use the underlying routine or the
wrapper is then up to them.
This would be more difficult, if you have, say, a matrix solver, that
halfway through, concludes that the matrix is near-singular and then stops.
This would not be something easily checked in advance.
Regards,
Arjen
Op vr 31 jul. 2020 om 19:08 schreef Martin Diehl <[email protected]>:
… I like the type(status) extensible type. But of course, what we really
need is proper exception handling in the language. Anything else is just a
stop gap solution. (like the current integer and string error messages of
some of the intrinsic routines). It's just not enough in 2020.
I agree that the error handling of Fortran is quite cumbersome. error stop
with a fixed error message (i.e no runtime information can be communicated
to the user) seems to be the best that the language offers. I have used error
stop in the os and os_path implementations and figured out that it gives
at least a reasonable stack trace.
For subroutines, the error handling by means of return values is quite
reasonable, but for functions with one return value it becomes annoying. If
I remember correctly, intent(out) is not even allowed in pure functions.
I would prefer if basic building blocks of the stdlib would use error stop
without more elaborated error handling. In addition, stdlib should provide
reasonable error handling/logging utilities that allow the user to deal
with unexpected situations. Consider the following case:
call chdir('this_dir_does_not_exist') ! will terminate with error stop
if(.not. isdir('this_dir_does_not_exist')) then
call stdlib_error_handler('this_dir_does_not_exist'// 'does not exist')
else
call chdir('this_dir_does_not_exist')
endif
This means extra work for the user, but all intrinsic statements (i.e.
open) work like that.
*If we would enforce error propagation by means of an error type, we would
essentially forbid the use of pure functions!*
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#224 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAN6YR342UMCE64AYSM3ROTR6L3B7ANCNFSM4PFPIXJQ>
.
|
@MarDiehl I'd personally would have very-very strong objections against letting low level I/O-routines die on error with In my opinion, there must be always an alternative to
How, the error is indicated/propagated/handled is of course a matter of taste (and topic of this issue 😉 ) and can even differ in each case, but Your proposed error handling for |
As for pure functions: I think, we only should allow pure functions, where:
Note that I am not referring here to programming errors as mentioned by @jvdp1 . If you pass the wrong dimension to a routine, it is OK if it calls |
You have convinced me that In also think that it makes sense to offer a stdlib solution for error handling (a special type/printing messages etc). However, I would like to avoid a strong coupling of individual parts from stdlib. Therefore, I'm opposing to use this error handling routine in basic routines like |
In the codes I am attempting to write, I find error handling to be a pervasive problem and think that strong coupling to library defined error handling to be unavoidable. I do not want each module to provide its own logging system. I therefore want the library to provide strong quality error handling routines. For other applications strong coupling to the file handling and os services may be unavoidable. |
Take our linalg as an example.
The very low level API is Lapack itself, which does not use derived types (thus there could be 15 arguments, but everything is essentially pure, no side effects) nor error stop, errors are propagated up, the user has to check them and handle them. This design ensures maximum reusability, it truly works for everybody.
The rest of the work is providing optional layers on top to simplify the API for some users, but some other users will always use Lapack directly.
The mid level API that I am hoping will go to stdlib is to provide simple Matlab/Python like functions such as y=solve(A, x). This has the following features: still no derived types, but it will fail with error stop is the matrix is singular, to ensure users code will never continue with bogus answers. We can optionally add a status variable, which if provided will propagate the error up. This API is for a subset of users who want to quickly prototype their code and just solve a matrix, not having to lose time figuring out the 15 arguments for lapack, and they want it to fail with error stop.
Then there can be an OO style API, like solver = Factorize(A); y=solver.solve(x).
There can be various ways to do error handling, different for each API. But it all starts having the procedural low level API that can be used as a building blocks for everything else.
…On Sat, Aug 1, 2020, at 7:51 AM, William B. Clodius wrote:
In the codes I am attempting to write, I find error handling to be a
pervasive problem and think that strong coupling to library defined
error handling to be unavoidable. I do not want each module to provide
its own logging system. I therefore want the library to provide strong
quality error handling routines. For other applications strong coupling
to the file handling and os services may be unavoidable.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#224 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAFAWCTYID2FTFMCZ3TXCLR6QMWNANCNFSM4PFPIXJQ>.
|
Do we want users to have the option of sending the error messages, if the optional STAT is not present, to their log files as well as the ERROR_UNIT of ERROR STOP? If we do then we want the library to consistently use our logging routines for error reporting. |
What I had in mind for the |
I would suggest to make it consistent with the language features: exit with |
But apart of this, I like the approach to have a error handling type for more involved/abstract functions and use plain integer for simple functions. |
and write, inquire, close, rewind, backspace, allocate, deallocate, the "atomic" subroutines, and basically anything else that can go wrong that the processor can detect. There are two limitations with error stop by itself for this application: first, in Fortran 08 it cannot accept variable strings as arguments; and, second, it is awkward by itself to send detailed messages. Both of these issues could be dealt with by having a logging system in the standard library. |
There is no simple dividing line between involved/abstract functions and simple functions. I like using the same approach for all procedures. |
To facilitate discussion of this issue I thought it would be useful to For my first example of a handling method I chose a flattened derived
As an alternative, I show the handling with a simple integer error
Finally I show a modified version of @aradi's code in which I extend
|
As for the third version, considering our earlier discussions, I'd suggest to use the error type directly, and its allocation status as status flag (not allocated: OK, allocated: error occured). This is more direct and probably also somewhat more efficient. So, it would be something like:
|
Before the file system layer (see #201, #220) in stdlib can be implemented, it would be important to reach some agreement on how to signalize errors within stdlib. I am not talking about logging, as this is an add on we can discuss later. I want to concentrate here on how errors can be passed between routines in stdlib.
Fortran's current approach to report status in I/O commands is to have some optional integer argument, returning the status of the operation. If the argument has not been specified, the code stops in case of errors. I'd propose to generalize this principle, but using derived types to achieve higher robustness and flexibility:
type(status)
), which methods allowing to set and query it.error stop
.class(error)
item. It can carry arbitrary types derived from a base class, so that arbitrary complex error information can be passed around. This way, the error signaling mechanism is extensible, we could even think to add an error-hierarchy as we have in Python.I have made a toy project to demonstrate the principle. Please, have a look at it and let me know what you think.
A few more notes:
change_dir()
).This issue is related to several other error discussons, e.g. #219 , #193, #95. My suggestion is, again, to first reach agreement on the low-level of the error reporting, and we can add extended functionality, such as logging, on top later.
The text was updated successfully, but these errors were encountered: