1

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.

Pythonist
  • 1,937
  • 1
  • 14
  • 25
  • 2
    Always use tag [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 Героям слава Jan 26 '16 at 21:43
  • 1
    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 Jan 26 '16 at 23:24
  • @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 Jan 27 '16 at 08:20
  • @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 Jan 27 '16 at 08:37

3 Answers3

1

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.

roygvib
  • 7,218
  • 2
  • 19
  • 36
  • The books is an example of techniques which rely heavily on Fortran 2003. – Vladimir F Героям слава Jan 26 '16 at 21:46
  • I have deleted the information about "Scientific Software Design"(because it is not within F90). I hope someone gives a more useful answer so that we can learn about the choice (actually, I was also trying to make a similar Timer thing...) – roygvib Jan 26 '16 at 22:30
  • I don't think it was necessary to delete that. – Vladimir F Героям слава Jan 26 '16 at 23:17
  • @VladimirF OK, then here is a [link](http://www.cambridge.org/us/academic/subjects/engineering/engineering-general-interest/scientific-software-design-object-oriented-way). AFAIK this book is often recommended so it may be worth cheking (I bought it but haven't read it through yet, so cannot say for sure about the contents...) – roygvib Jan 27 '16 at 06:54
  • Allowing the explicit creation of Timer instances, plus providing Tic/Tac routines, is actually the option a). It follows more canonically in my opinion the OOP. However, it goes against the "encapsulation" rule, which is followed more sharply in option b). I know that my question is a bit ill-posed and admits different answers, but my aim is to get someone seasoned in OOP with Fortran to guide me to achieve the most "standard/orthodox/modern/reliable/robust" design. Thus, although your answer is helpful, I would like to get further opinions. Thanks. – Pythonist Jan 27 '16 at 08:29
  • Just one note: I think it is better to give your definition of "encapsulation" more clearly in your Question, because it seems a bit different from the usual one. Also, my last sentence in the answer above is not option a), but a hybrid of a) and b) (note that the Tic/Tac service routines are intended to be non-type-bound procedures, so it can be used the same way as b) from the user's side. On top of it, we can define type-bound ones (and the routines can be re-used there) to allow an instance-based usage. But anyway, I hope other people will give you more useful info :) – roygvib Jan 27 '16 at 09:02
  • By "encapsulation" I mean that I create a "black box" for the user. He has only indirect access to its contents through an interface, which is ideally as small as possible, and he does not need to understand the box it to use it. Option b) is more encapsulated because it makes the user able to use the module without he having to know that he is actually using a user defined type and its name, and the interface is simplified to just a sting for labelling the Timer. I'll edit the question accordingly. – Pythonist Jan 27 '16 at 09:03
  • Sorry, I didn't follow you, although it sounds important. What is a non-type-bound procedure? – Pythonist Jan 27 '16 at 09:08
  • 1
    @Onturenio Hi, I meant by a non-type-bound procedure a subroutine like "call Tic( str )", i.e., something like those used in your example code. We can create the usual OO stuff as explained in Vlad's answer (F03 version), but this is not very handy if the user does not want to even declare objects. In this case we can provide non-type-bound service routines like Tic() that handles everything (including object creation) inside a module, so that the user does not need to declare Timer objects. But the cost to pay is that the implementation becomes more tedious. – roygvib Jan 27 '16 at 20:40
1

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

  • OK, now I realise that what I'm doing is using the Module as an object. The small difference with what you say is that my module actually contains a dynamic array of Timers, which are set with the call to Tic, rather than just one Timer. OK, so I guess separating the state from the functionality is the "orthodox" approach. I'll go for it then, and indeed I'll use the Fortran 2003 version. Thanks. – Pythonist Jan 27 '16 at 12:47
1

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.

Community
  • 1
  • 1
pch
  • 72
  • 3