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.