2

It seems to me that one of the nice features of submodules is that you can create a helper function in a submodule at very little cost to the programmer; you don't trigger a compilation cascade, you don't clutter the namespace or your documentation, and it is immediately clear where that function can and can't be used. They're like nicer versions of private functions.

However, functions in submodules cannot be used. While this is working as intended, it also appears that this prevents the function from being unit tested. Both unit test frameworks that I'm aware of, pFUnit and fruit, require use syntax to operate.

There are some (imho somewhat inelegant) workarounds for the same problem with private functions discussed in How to access private variables in fortran modules?, but none of those solutions appear to work for functions in submodules, at least not without negating all of the benefits of putting those functions in submodules in the first place.

So are there any solutions to this problem?

veryreverie
  • 2,871
  • 2
  • 13
  • 26
  • Are these helper function actually used someplace within the module? If no, then delete them as they're wasted baggage. If yes, then the answer should be obvious. You need to `USE` the routines from the module that require the helper functions. – steve Feb 26 '21 at 17:39
  • @steve The helper functions are used by other functions in the submodule, which have interfaces in the module and so can be `use`d. But the helper functions themselves can't be `use`d outside the submodule. `use`ing the routines that require the helper functions lets me unit test those routines, but not the helper functions themselves. – veryreverie Feb 27 '21 at 11:42
  • defining the helper functions in some middle-layer(?) module (between the module and the submodule) might possibly help? – roygvib Feb 27 '21 at 17:47
  • Ah, yeah, I know the helper functions cannot be accessed directly via the module. You need to write the unit tests for routines exposed by the module that will exercise each path through the helper functions. If `FOO(x)` is the routine from the module, and `HELPER1(X)` and `HELPER2(X)` are submodule helper functions, then you write unit tests for `FOO(X)` that exercise each helper function. – steve Feb 27 '21 at 18:45
  • @roygvib I like the idea but I'm not sure if it works. Can you give an example where adding a function to the middle layer module doesn't trigger a compilation cascade? – veryreverie Feb 28 '21 at 09:05
  • @steve I agree that in principle exhaustively testing `FOO` will exhaustively test `HELPER1` and `HELPER2`, but in practice I think that if it makes sense to separate off a helper function then it usually makes sense to unit test that helper function separately. Particularly if multiple functions in the submodule are using the same helper function. – veryreverie Feb 28 '21 at 09:10

2 Answers2

2

The primary purpose of introducing the submodule concept was to avoid long recompilation cascades when only a minor non-interface-breaking change had been introduced to a module file. This is done by separating the interface of the procedures and putting them in the parent module and keeping the implementation in the submodule.

Submodules are themselves permitted to have submodules, which is useful for very large programs. The number of levels of submodules that I have seen typically does not exceed two (that is, a module with submodules that themselves have submodules) but there is no limit. Each module or submodule is the root of a tree whose other nodes are its descendants and have access to it by host association. No other submodules have such access, which is helpful for developing parts of large modules independently. Furthermore, there is no mechanism for accessing anything declared in a submodule from elsewhere – it is effectively private, as you said.

Therefore, in summary, if you need anything from any submodule at any level to be accessed by any other parts of the program, it must be declared in the original parent module of the submodule. There is no other way, as far as I am aware to access anything in any submodule whose interface or declaration is not given in the original parent module.

Once you put the procedure interfaces and variable/type declarations in the parent module, you can use them anywhere in your program, even though the procedure implementations could be buried in submodules of the parent module.

Scientist
  • 1,767
  • 2
  • 12
  • 20
  • It feels like there should be a solution which involves defining the unit tests for a given submodule in a submodule of that submodule. But then I can't work out how to get those tests into the queue of tests to run. And even if this is theoretically possible, to my knowledge it isn't supported by testing frameworks at the moment. The information is useful though, thank you. – veryreverie Mar 01 '21 at 13:58
1

I have little experience with submodules (so not sure if this is useful), but just to extend my comment above...

!! parent_mod.f90 (public things)
module parent_mod
    implicit none

    type myint_t
        integer :: n = 100
    contains
        procedure :: add, show
    endtype

    interface
        module subroutine add( x )
            class(myint_t) :: x
        end
        module subroutine show( x )
            class(myint_t) :: x
        end
    endinterface
end

!! parent_helper.f90 (private things to be used by parent_impl.f90
!! and possibly by unit tests)
module parent_helper
    use parent_mod, only: myint_t
    implicit none
contains
    subroutine debug_show( x )
        type(myint_t) :: x
        print *, "debug: x = ", x
    end
end

!! parent_impl.f90  (implementation)
submodule (parent_mod) parent_impl
    implicit none
contains
    module procedure add
        x% n = x% n + 1
    end
    module procedure show
        use parent_helper, only: debug_show
        call debug_show( x )
    end
end

!! main.f90
program main
    use parent_mod, only: myint_t
    implicit none
    type(myint_t) :: a

    call a% add()
    call a% show()  !! 101

    call a% add()
    call a% show()  !! 102

    block
      use parent_helper
      call debug_show( a )  !! 102
    endblock
end

!! build
$ gfortran-10 -fcheck=all -Wall -Wextra parent_mod.f90 parent_helper.f90 parent_impl.f90 main.f90

Does this possibly help avoid recompilation of parent_mod.f90 (even when parent_helper or parent_impl are modified)? (And I noticed that the module name "parent" has no meaning here... XD)

roygvib
  • 7,218
  • 2
  • 19
  • 36
  • I didn't think of having modules which are only `use`d by submodules and not by other modules. I can't see any downsides of moving helper functions out of submodules and into these modules, so thank you, this solves my problem. – veryreverie Mar 01 '21 at 13:55