All you need to do to achieve the above goal is to define PURE as an empty preprocessor macro, or even better yet, redefine it as impure,
write(*,*) "function returns:", square(2.)
PURE function square(x) result(xSq)
real, intent(in) :: x
real :: xSq
xSq = x**2
write(*,*) "impure function print of xSq: ", xSq
ifort -fpp -DPURE=
to get impure definition or with
ifort -fpp -DPURE=pure
to get a pure function definition. We have been using this approach for quite some time in our codebase, and it works great for purity-performance checks or debugging.
The above example is just an illustration, one can write a more elegant solution that also works with elemental.
I think this would be useful for logging and especially for debugging. Sure, the “right” thing to do would be to attach a debugger, but there are also cases where I actually would prefer print based debugging. For example if the problematic procedure is invoked many times before the issue occurs. This is often the case when developing simulation software which typically repeat the same code many times with slightly altered input.
pure subroutine some_pure_sub()
! Do regular things here...
! Then some debug output:
use debug_utils, only: dbg
call dbg('Hello from some_pure_sub' // new_line('c') &
// 'This can be useful for debugging pure procedures')
I have some other ideas to make it even more useful, but that will be for another day…
I would be extremely unhappy with the compiler/runtime if it actually did that out of my control regardless whether the procedure was pure or not. Luckily I don’t think that is or will ever be the case.
Thanks, @epagone. That is certainly an interesting idea. I think I will keep the separate repo for a bit as I have some ideas I’d like to test which might not be very “stable” on the first go… You are more than welcome to copy the code into stdlib in the meantime if you wish though.
Also, I do wonder if this actually fits well into a separate library. Paranoid users can then comment out that particular dependency in their fpm.toml file once done with debugging. If they’ve accidentally forgot to remove some debug code this will be caught at compile time.
Many thanks for making your code available for integration into stdlib. Unfortunately, I vaguely recall a few instances where there has been already significant discussion in places on “debug” and “release”-mode features of stdlib, so your (legitimate) doubt applies to other parts of the library and it has been identified. This issue and the integration with related features already implemented (e.g. the procedure check) make it difficult to simply copy and “drop-in” the code into stdlib.
I understand that you might prefer to experiment in a separate repository (actually I think it’s a good idea) but I was suggesting that, if you want, you might adapt your routines to work with stdlib as they mature more.
You should be aware that compiler makes a plethora of nasty things with your code, especially when high optimization levels are requested – doing such things is actually the principal way of code optimization.
“Pure” keyword by itself does not amend what the procedure is actually doing – just omit “pure” and you will get the same behavior. As Fortran has never been a language for computer science theoreticians (no need to introduce functional purity for the sake of the functional purity itself), the primary intent of this language feature is to help the optimizing compiler to optimize better by explicitly allowing a number of nasty tricks to be applied – enjoying the explicit assertion that the procedure depends only its the arguments and changes nothing in the world except the “returning values” (be it the value returned by function or dedicated parameter(s) of the subroutine), which, in turn, allows to invoke it in arbitrary order instead of following the order suggested by the author of the program.
Sure, optimization will do a lot of “nasty” things to the code, but at least Intel will as far as I can see not introduce multithreading unless explicitly asked to do so with the -parallel flag. This is good because for many scenarios, parallelization by running multiple processes might be more relevant.
You can’t rely on this. Actually, you can’t rely on:
the assumption that you or somebody else will always compile your code with the specific compiler;
the assumption that you or somebody else will never use your subprogram in various parallelization schemes, including the most weird ones.
Again, pure subprograms are introduced for the possibility to use them safely in parallelizations and control flow rearrangements, avoiding (by design) various issues like race conditions, “heisenbugs” and other creepy stuff you never want to deal with.
It is interesting question if some kind of “IO monad” could be implemented in Fortran – if anybody ever decides to go really functional. Otherwise, returning sign of error or error message via argument of pure subroutine, or as a field of user-defined class, or via NaN value, followed by logging outside the scope of the pure subprogram, could be the options. Indeed, it is inconvenient, but nobody forces to use pure functions if they are inconvenient in the specific case.
Unfortunately, that does not always work. As soon as you have a do concurrent loop, which allows calls to pure procedures only, removing the pure attribute from the called procedure (just to be able to print/write debug information) would result in code which might be (should be) rejected by the compiler…
I very much like @rwmsu’s proposal that a file may be opened with the PURE attribute and may then be written to, but not read by a program. Writes to such a file would then not violate the principles of PURE procedures.
There is a problem with ELEMENTAL procedures. Should the WRITE take place for every instance or only once in an elemental call? And would we need new syntax to choose? Elemental procedures can invoke other elemental procedures. Would a WRITE(pure_unit,format,ONCE…) construct write only once for a call of the outer elemental routine when calls are nested? This would not be easy to implement.