Is it possible to do something similar to "typeclass" behaviour in Fortran?

118 views Asked by At

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

1

There are 1 answers

2
mcocdawc On BEST ANSWER

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.