I/O in pure Fortran procedures
Asked Answered
S

2

10

I'm trying to incorporate error checking within a pure procedure I am writing. I would like something like:

pure real function func1(output_unit,a)
    implicit none
    integer :: a, output_unit

    if (a < 0) then
        write(output_unit,*) 'Error in function func1: argument must be a nonnegative integer. It is ', a
    else
    func1 = a/3

    endif
    return
end function func1

However, pure functions are not allowed to have IO statements to external files, so I tried passing a unit number to the function, e.g. output_unit = 6, which is the default output. gfortran still regards this as illegal. Is there a way around this? Is it possible to make the function a derived type (instead of intrinsic type real here) which outputs a string when there is an error?

Sclerenchyma answered 7/1, 2012 at 3:50 Comment(9)
Technically, a "pure" procedure does not in any way modify its environment or any of its operands.Bewhiskered
So it is not supposed to print to screen? Can it print to an internal string which the main program can access and print to screen? That's rather roundabout, though.Sclerenchyma
I don't know exactly what Fortran enforces when "pure" is specified, but I suspect it won't let you do any of that. Can't you simply drop the "pure"?Bewhiskered
@SamuelTan - Nope - no I/O of any kind in PURE. In short, it doesn't alter anything (variable, local or global), it doesn't SAVE, and it doesn't I/O anything (would've been easier to make a list of the things it does do :\ ... ;)Leesen
The usual way around these restrictions is to turn the function into a subroutine and add an extra argument that returns the status (you can use a derived type for this argument, to return both an error code and error text).Callihan
@HotLicks. I'm using this function in a do concurrent block, so it has to be pure (I have to explicitly put pure in front, otherwise the compiler complains).Sclerenchyma
@eriktous. That's an idea I've thought about (see last two lines of question). However, it would be rather tedious to print 'No problem with func1' every time I call the procedure, wouldn't it? Maybe I could assign an error message if the input is faulty, and a blank otherwise to the error string.Sclerenchyma
Well, that's why you also return an error code. You check that first, and if it indicates 'no error', you don't print anything and simply continue; if it indicates an error, you print the message.Callihan
@SamuelTan - I'm trying to clean up the pure tag, which sometimes refers to pure, ot to pure or to "pure CSS". I don't know Fortran, so if my edit doesn't make sense please let me know!Heffner
M
5

You are not the first person to have this problem, and I'm happy to say that this flaw in the standard will be remedied in Fortran 2015. As stated in this document (page 6, header "Approved changes to the standard"), "the restriction on the appearance of an error stop statement in a pure procedure should be removed".

The Fortran 2008 standard included the error stop statement in the context of some new parallel computing features. It signals an error and makes all processes stop as soon as is practicable. Currently, neither stop nor error stop statements are allowed in pure procedures, because they're obviously not thread-safe. In practice this is unnecessarily restrictive in cases where an internal error occurs.

Depending on your compiler, you may have to wait patiently for the implementation. I know that Intel has implemented it in their ifort compiler. ("F2015: Lift restriction on STOP and ERROR STOP in PURE/ELEMENTAL procedures")

alternative

For an alternative approach, you could have a look at this question, though in you case this is probably slightly trickier as you have to change the do concurrent keyword, not just pure.

(end of proper answer)

if getting dirty hands is an option ...

In the meantime you could do something brutal like

pure subroutine internal_error(error_msg)
    ! Try hard to produce a runtime error, regardless of compiler flags.
    ! This is useful in pure subprograms where you want to produce an error, 
    ! preferably with a traceback.
    ! 
    ! Though far from pretty, this solution contains all the ugliness in this 
    ! single subprogram.
    ! 
    ! TODO: replace with ERROR STOP when supported by compiler
    implicit none

    character(*), intent(in) :: error_msg

    integer, dimension(:), allocatable :: molested

    allocate(molested(2))
    allocate(molested(2))
    molested(3) = molested(4)
    molested(1) = -10
    molested(2) = sqrt(real(molested(1)))
    deallocate(molested)
    deallocate(molested)
    molested(3) = molested(-10)
end subroutine internal_error

Should anyone ask, you didn't get this from me.

Mixture answered 22/6, 2016 at 14:28 Comment(3)
The brutal subroutine is unnecessarily complicated. You can as well just write an external subroutine and lie in an interface that it is pure. Extremely simple.Gosnell
Would that be standard-conforming?Mixture
Of course not, but I do not think it is harmful considering what it does. When it aborts the program it will not spoil the rest of the run in any way. And neither does the standard (especially the 95 one) specify what happens when you make a square root of a negative number. In IEEE conforming CPUs with Fortran 2003 you do have some guarantees only if you do actually set the right flag to enable halting on an IEEE exception. But often one does not want that everywhere. I certainly do not run my main code with halting on floating point exceptions.Gosnell
S
-1

I've found an answer myself, detailed here. It uses what is considered "obsolescent", but still does the trick; it is called alternate return. Write the procedure as a subroutine as it doesn't work on functions.

pure real subroutine procA(arg1)
    implicit none
    integer :: arg1

    if (arg < 0) then
        return 1 ! exit the function and go to the first label supplied
                 ! when function was called. Also return 2, 3 etc.
    else
        procA = ... ! whatever it should do under normal circumstances
    endif
endsubroutine procA

.... 

! later on, procedure is called
num = procA(a, *220)

220 write(6,*) 'Error with func1: you've probably supplied a negative argument'

What would probably be better is what eriktous suggested--get the procedure to return a status, perhaps as a logical value or an integer, and get the program to check this value every time after it calls the procedure. If all's well, carry on. Otherwise, print a relevant error message.

Comments welcome.

Sclerenchyma answered 9/1, 2012 at 12:36 Comment(1)
Argh! Please don't use this. There are reasons for it being labeled obsolescent. The current standard suggests the following as alternative (basically the same as my suggestion): The same effect can be achieved with a return code that is used in a SELECT CASE construct on return.Callihan

© 2022 - 2024 — McMap. All rights reserved.