Moduły i programy#

Moduły są preferowanym sposobem tworzenia nowoczesnych bibliotek i aplikacji Fortran. Dobrą praktyką jest, aby jeden plik źródłowy zawierał tylko jeden moduł, a nazwa modułu powinna odpowiadać ścieżce pliku, aby ułatwić nawigację w większych projektach. Zaleca się również, aby do nazw modułów dodać prefiks z nazwą biblioteki, aby uniknąć kolizji nazw, gdy są używane jako zależności w innych projektach.

Oto przykładowa nazwa takiego modułu

!> Interface to TOML processing library.
!>
!> ...
module fpm_toml
  use fpm_error, only : error_t, fatal_error, file_not_found_error
  use fpm_strings, only : string_t
  use tomlf, only : toml_table, toml_array, toml_key, toml_stat, get_value, &
    & set_value, toml_parse, toml_error, new_table, add_table, add_array, &
    & toml_serializer, len
  implicit none
  private

  public :: read_package_file
  public :: toml_table, toml_array, toml_key, toml_stat, get_value, set_value
  public :: new_table, add_table, add_array, len
  public :: toml_error, toml_serializer, toml_parse

contains

  !> Process the configuration file to a TOML data structure
  subroutine read_package_file(table, manifest, error)
    !> TOML data structure
    type(toml_table), allocatable, intent(out) :: table
    !> Name of the package configuration file
    character(len=*), intent(in) :: manifest
    !> Error status of the operation
    type(error_t), allocatable, intent(out) :: error
    ! ...
  end subroutine read_package_file

end module fpm_toml

Jest kilka rzeczy, które powinny zostać podkreślone w powyższym przykładzie. Po pierwsze, każdy moduł zaczyna się komentarzem, który określa cel i zawartość modułu. Każda procedura również zaczyna się komentarzem, który zwięźle opisuje jej cel oraz intencję fikcyjnych argumentów. Dokumentacja jest jednym z najważniejszych części tworzenia długotrwałego oprogramowania niezależnie od języka.

Po drugie, importy (use) oraz eksporty (public) są wyraźnie podane, co pozwala na szybkie sprawdzenie dostępnych procedur, stałych i typów pochodnych. Importy są zazwyczaj ograniczone do zakresu modułu, a nie ponownie importowanie w każdym zakresie procedury lub interfejsu. Podobnie, eksporty są wykonywanie jawne poprzez dodanie instrukcji private w pojedynczym wersie i wymieniają wszystkie eksportowane symbole w instrukcjach public.

Na koniec, instrukcja implicit none działa w zakresie całego modułu i nie ma potrzeby powtarzania jej w każdej procedurze.

Zmienne wewnątrz modułu są statyczne (niejawnie zapisane). Zdecydowanie poleca się ograniczenie stosowania w module zmiennych na rzecz wyrażeń stałych, takich jak parametry lub enumeratory oraz eksportowanie ich jako protected, a nie public.

Podmoduły mogą być używane do łamania długich łańcuchów zależności oraz skracania kaskad rekompilacji w programach Fortran. Oferują również możliwość dostarczania specjalistycznych i zoptymalizowanych implementacji bez konieczności używania preprocesora.

Przykładem ze standardowej biblioteki Fortran jest moduł kwadraturowy, który definiuje tylko interfejsy procedur modułu, a nie implementacje

!> Numerical integration
!>
!> ...
module stdlib_quadrature
  use stdlib_kinds, only: sp, dp, qp
  implicit none
  private

  public :: trapz
  ! ...

  !> Integrates sampled values using trapezoidal rule
  interface trapz
    pure module function trapz_dx_dp(y, dx) result(integral)
      real(dp), intent(in) :: y(:)
      real(dp), intent(in) :: dx
      real(dp) :: integral
    end function trapz_dx_dp
    module function trapz_x_dp(y, x) result(integral)
      real(dp), intent(in) :: y(:)
      real(dp), intent(in) :: x(:)
      real(dp) :: integral
    end function trapz_x_dp
  end interface trapz

  ! ...
end module stdlib_quadrature

Implementacja jest zapewniona w osobnym podmodule, takim jak przedstawiona poniżej reguła całkowania trapezowego.

!> Actual implementation of the trapezoidal integration rule
!>
!> ...
submodule (stdlib_quadrature) stdlib_quadrature_trapz
  use stdlib_error, only: check
  implicit none

contains

  pure module function trapz_dx_dp(y, dx) result(integral)
    real(dp), intent(in) :: y(:)
    real(dp), intent(in) :: dx
    real(dp) :: integral
    integer :: n

    n = size(y)
    select case (n)
    case (0:1)
      integral = 0.0_dp
    case (2)
      integral = 0.5_dp*dx*(y(1) + y(2))
    case default
      integral = dx*(sum(y(2:n-1)) + 0.5_dp*(y(1) + y(n)))
    end select
  end function trapz_dx_dp

  ! ...
end submodule stdlib_quadrature_trapz

Zauważ, że procedury modułu nie muszą być zaimplementowane w tym samym podmodule. Wiele podmodułów może zostać użytych, aby zmniejszyć obciążenie kompilacji dla ogromnych modułów.

Na koniec, podczas konfigurowania programu zaleca się zachowanie jak najmniejszej liczby implementacji w programie. Ponowne używanie implementacji z modułów pozwala na pisanie kodu wielokrotnego użytku oraz skupieniu jednostki programu na przekazywaniu danych wejściowych użytkownika do odpowiednich funkcji biblioteki i obiektów.