1

I am aware one can use an interface to create a single name for multiple functions. In fact, I've done so in a small code for something like that:

interface assert_equal
  procedure assert_equal_int
  procedure assert_equal_real
  procedure assert_equal_string
end interface

However, I noticed that most of the time I just need the first two dummy arguments of the function to implement some sort of equality. If I was trying to do this in Haskell, for instance, I would do something like

assertEqual :: (Eq a) => a -> a -> String -> IO ()

Where the first two arguments are the values being checked against each other and the third one is a message.

Or in Rust I would do, for instance

impl {
  fn assert_equal<T>(expected: T, actual: T, msg: &str) where T: std::Eq {
    ...
    # enter code here
  }
}

Is there something similar or that at least emulates the same behaviour in Fortran? The current issue I am trying to solve is being able to do some sort of assert_equal in derived types that I will create in my code base, without needing to define a specific subroutine for each derived type when they are going to look so similar

lsoranco
  • 125
  • 9
  • 2
    Would templating [approaches/hacks](https://stackoverflow.com/q/23706742/3157076) work? – francescalus Aug 17 '23 at 20:59
  • I suppose it is something, and I haven't really considered it. I think it would still imply creating a small stub for every derived type. And the main issue: I don't want the module to know about derived types. I was looking at "pFUnit" for inspiration and that looks like straight up magic to me! I have no idea how the person who coded that manage to make `@assertEqual` work as I intend. It is like he created another language inside Fortran. – lsoranco Aug 17 '23 at 21:06
  • 1
    There is also an old technique with the `transfer` intrinsic to achieve generic programming in fortran: [see this link](https://fortranwiki.org/fortran/show/Generic+programming) – Rodrigo Rodrigues Aug 26 '23 at 22:01

1 Answers1

1

With fypp you can quite easily achieve such a behaviour:

#:def assert_equal(a, b, message="")
    if (any([${a}$ /= ${b}$])) then
        write(*, *) "assert_equal violated"
        write(*, *) ${a}$, '/=', ${b}$
#:if message
        write(*, *) ${message}$
#:endif
        error stop
    end if
#:enddef

program test_assert
    implicit none

    @:assert_equal(4, 4)
    @:assert_equal([3, 4], [3, 4])
    @:assert_equal(4., 4.)
    @:assert_equal("4", "4")

    @:assert_equal("4", "5", "this is my cool message")

end program

Which can be compiled via fypp test_assert.fpp > test_assert.f90 && gfortran test_assert.f90

Note that because we use any([${a}$ /= ${b}$]) instead of ${a}$ /= ${b}$ it works for both scalar and array values, as long as a /= operation is defined.

Note that for most numerical codes I would actually not use an assert_equal like this:

  1. Floating point numbers should be compared with some |a - b| < epsilon criterion.
  2. The equality of large floating point arrays is better determined by equality under some matrix norm (e.g. Frobenius norm sum((A - B)**2) < epsilon) instead of just ensuring element wise equality.

For this reason I would rather recommend to only supply an assert macro. Then developers will write something like:

@:assert(near_zero(sum((A - B)**2))))

when comparing two floating point arrays A and B.

PS: We use a similar approach in a largeish quantum chemistry program here.

mcocdawc
  • 1,748
  • 1
  • 12
  • 21
  • Why not try to do this with the, more common , C / C++ preprocessor (using `#define`)? Means also that you don't need to install Python for this. – albert Sep 01 '23 at 09:26
  • 1
    Because there was never a standardized preprocessor for Fortran, hence implementations chose whatever they liked. Since `gcc` as one of the most important compilers supports only the Pre-ANSI C-preprocessor you will quickly hit a wall for anything that is more complicated than `#ifdef COMPILE_FLAG_` Feel free to generate a working example that compiles with `gfortran` and its preprocessor. – mcocdawc Sep 01 '23 at 12:10