1
votes

I'm writing a short module in Fortran 90/2003 that provides a simple and user friendly interface for counting time between different parts of the execution of a program. Inspired by the tic, tac commands in Matlab, the idea is that the user uses the module in a program as follows:

program test
use Timer
call Tic("timername")
! some heavy stuff
call Tac("timername")
end program test

Now, I know how I can achieve that result with Fortran intrinsics. My question is how I should do it. I'm doing this to learn good design practices, rather than about Fortran syntax.

I have defined a user defined variable called Timer, which is the main object that I use to implement the functionality. However there are (at least) two different ways to use this object to let the user using several timers:

a) I can make the user defined variable Timer public, and then force the user to create timers object by hand. The user has to create as many timers as he needs, and then use methods to handle them.

b) I can hide this type by making it private. Then, to store different timers, I create an array of Timer objects in the module as a global variable, although private for the module, and every time the user calls the subroutine Tic, a new timer is defined in this array. In the example above, the user is using a module implemented following the latter approach (note that the program does not use keyword type at all).

Although the two options work technically (I have already implemented both) each one has advantages and caveats, and the rules I know about software design somehow collide. I wonder which is the best approach from a "orthodox" point of view.

Option a) has the advantage of following better the OOP: the user creates explicitly and object and operates with it. It does not use any global variable.

Option b) has the advantage of being more strongly "encapsulated". By this I mean that the user does not even need to know what a Timer is, and even about the existence of it. Further, the interface provided to interact with Timer objects is just a simple string, making the whole module more opaque for the user, which does not need to define Timer variables on purpose. He/she just uses two subroutines provided by the module that accept a string as input. That's all. The problem is that I have the feeling that this design, based on an array defined for the whole module, goes against the rule of avoiding global variables. It's not a real global variable, since it is private, but still.

So having this two options, which should I go for to produce the most orthodox approach?

PS: maybe there is a third option, which allows the user to create objects indirectly without having access the user defined type (i.e. not just define elements in an existing array, as done in the solution b). I don't know if this is possible at all to create variables at run time. Any idea in this direction is also very welcome.

3
Always use tag fortran. Use the version tags to specify a specific version if appropriate. Be advise that there is no true OOP in Fortran 90, only some rudimentary techniques are possible. True OOP only comes with Fortran 2003. Are you sure you want to limit the answers to Fortran 90 only (25 years old language, older than many people here!). - Vladimir F
You can put your module variable (b) in a public data-type and pass this around. This would allow you to have multiple such objects while maintaining the possibility to create timers within it easily. This would shift the burden from creating individual timing objects to creating a timing context just once. In this scenario you could still use a module variable of the timing context object and use that as a default in your routines. - haraldkl
@VladimirF the differences between 90 and 2003 become apparent in more advanced features such as heritage or polymorphisms. In this small module, I think there are no differences between 90 and 2003, and the doubts that made me rise my question are independent on this level of detail. Thus, I'm going to edit the question not to restrict it to fortran 90. Thanks for the hints. - Pythonist
@haraldkl if I understood you, the scenario you describe is a). Now my question is whether it would be preferible to use scenario b), where the module is more strongly encapsulated and the user does not even need to know about the existence of Timer objects. He/she can create new timmers on demand without having to define them explicitly at the beginning of the program. Option b) is better from a user point of view, but perhaps not very orthodox for using a global variable. Both work technically, but which is the best design in general? That's my question. - Pythonist

3 Answers

1
votes

Usually, a good design is to hide the details of the implementation to the user. This is encapsulation.

This means you have some "object", you don't expose the details about its internal state, but only some ways how to use such object.

1. Module as object

In Fortran 90/95 the OOP was somewhat limited. One approach was to have a module which was this "object". The module variables was the internal state and the module procedures implemented the functionality and used the module's internal state. In this design you do not expose the module variables if not necessary. The problem is that you can always have just one instance of the object - the module.

This would be:

use Timers, only: Tic, Tac
call Tic()
! some heavy stuff
call Tac()

2. Derived type as object

Another classical way is to have a derived type, which contains the state in its components. In that case you can have multiple instances of the "object" - multiple variables with the type of the object. When you do any operation with the object, you call a module procedure from the module which defines the object and you always pass the object instance as an argument - often as the first one.

use Timers, only: Timer, Tic, Tac
type(Timer) :: t

call Tic(t)
! some heavy stuff
call Tac(t)

Your question’s code

use Timers, only: Tic, Tac

call Tic("timername")
! some heavy stuff
call Tac("timername")

or some variation like

use Timers, only: Tic, Tac

call Tic(1)
! some heavy stuff
call Tac(1)

is functionally similar, but strange. Why should the module which implements the functionality store also the state? Can't there be a clash when using this module from more places? I would definitely let the user to create the instances himself.

3. Fortran 2003

In this very simple example Fortran 2003 does not change that much, if you already expose the type. Again, the state is in the components of the derived type. But you can bind the procedures, which work with the type, to this type directly and you don't have to import them separately. You just use the type and every functionality, overloaded operators and similar, comes with it:

use Timers, only: Timer
type(Timer) :: t

call t%tic()
! some heavy stuff
call t%tac()

You can see that the most modern approach definitely exposes the Timer type to the user.


When you do expose the type, you can make the components private and use only the constructor and other associated procedures (optionally type-bound) to manipulate them (getters/setters and others).

1
votes

Though I don't know much about OOP, I guess that there is nothing like "the most orthodox approach" (because Fortran allows OOP but does not enforce it). The choice also seems to depend on the need for creating multiple instances of Timer with the same character string (e.g., in parallel run?). In this case the option (a) may be more conveinent, while the option (b) seems more handy otherwise. Merging the two approaches is also possible by allowing the explicit creation of Timer objects by the user, while providing handy tic()/toc() routines that automatically create/manipulate necessary objects within a module.

1
votes

Yes, data encapsulation and hiding are considered to be good practices in software design. We can achive that in Fortran by creating a derived type such that an instance of the type (the object) is opaque. Consider the following module

module SomeModule

  implicit none

  private
  public :: SomeType

  type SomeType
     private
     integer :: n
     real :: x
  end type SomeType

end module SomeModule

Note that SomeType is declared as public whereas the contents of the type are private. Now, when I can create an object of type SomeType

use SomeModule, only: SomeType
type(SomeType) :: st

the object st is opaque - I can create it, pass it around, but can not access its contents. The only way I can edit the contents of st is through a routine contain-ed in the module SomeModule.

I have a more concrete example here.