Search code examples
genericsfortrangeneralization

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


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


Solution

  • 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.