Организация структуры кода#

Большинство языков программирования позволяют объединять часто используемый код в подпрограммы, которые можно повторно использовать, вызывая их из других частей кода.

В языке Fortran доступно два вида подпрограмм:

  • Процедура: вызывается оператором call

  • Функция: вызывается внутри выражения или присваивания, которому она возвращает значение

И процедуры, и функции имеют доступ к переменным в родительской области видимости по ассоциации имён аргументов; если не указан атрибут value, то это похоже на вызов по ссылке.

Процедуры#

Входные аргументы процедур, известные как фиктивные (пустые) аргументы, указываются внутри круглых скобок после имени процедуры; типы и атрибуты фиктивных аргументов объявляются в теле процедуры так же, как и её локальные переменные.

Пример:

! Print matrix A to screen
subroutine print_matrix(n,m,A)
  implicit none
  integer, intent(in) :: n
  integer, intent(in) :: m
  real, intent(in) :: A(n, m)

  integer :: i

  do i = 1, n
    print *, A(i, 1:m)
  end do

end subroutine print_matrix

Обратите внимание на дополнительный атрибут intent при объявлении фиктивных аргументов; этот необязательный атрибут указывает компилятору, доступен ли аргумент „только для чтения“ (intent(in)), „только для записи“ (intent(out)) или „для чтения и записи“ (intent(inout)) внутри процедуры. В этом примере процедура не изменяет свои аргументы, поэтому все аргументы имеют атрибут intent(in).

Хорошей практикой является всегда указывать атрибут intent для фиктивных аргументов; это позволяет компилятору найти непреднамеренные ошибки и обеспечивает самодокументирование кода.

Мы можем вызвать процедуру из программы с помощью оператора call:

program call_sub
  implicit none

  real :: mat(10, 20)

  mat(:,:) = 0.0

  call print_matrix(10, 20, mat)

end program call_sub

Этот пример использует так называемый аргумент в виде explicit-shape массива (явной формы), так как мы передали дополнительные переменные для описания размеров массива A; это не потребуется, если мы поместим нашу процедуру в модуль, как описано ниже.

Функции#

! L2 Norm of a vector
function vector_norm(n,vec) result(norm)
  implicit none
  integer, intent(in) :: n
  real, intent(in) :: vec(n)
  real :: norm

  norm = sqrt(sum(vec**2))

end function vector_norm

В коде программного продукта следует, вместо приведённой в примере, использовать встроенную функцию norm2.

Чтобы вызвать эту функцию:

program run_fcn
  implicit none

  real :: v(9)
  real :: vector_norm

  v(:) = 9

  print *, 'Vector norm = ', vector_norm(9,v)

end program run_fcn

Хорошей практикой программирования является когда функция не изменяет свои аргументы — то есть, все аргументы функции должны иметь атрибут intent(in). Такие функции называют pure (чистыми) функциями. Используйте процедуры, если ваша подпрограмма должна изменять свои аргументы.

Модули#

Модули языка Fortran содержат определения, которые становятся доступными для программ, подпрограмм и других модулей с помощью оператора use. Они могут содержать объекты данных, определения типов, подпрограммы и интерфейсы.

  • Модули позволяют управлять областью видимости, когда доступ к объектам организуют явно

  • Модули автоматически генерируют явные интерфейсы, необходимые для современных подпрограмм

Рекомендуется всегда размещать функции и процедуры внутри модулей.

Пример:

module my_mod
  implicit none

  private  ! All entities are now module-private by default
  public public_var, print_matrix  ! Explicitly export public entities

  real, parameter :: public_var = 2
  integer :: private_var

contains

  ! Print matrix A to screen
  subroutine print_matrix(A)
    real, intent(in) :: A(:,:)  ! An assumed-shape dummy argument

    integer :: i

    do i = 1, size(A,1)
      print *, A(i,:)
    end do

  end subroutine print_matrix

end module my_mod

Compare this print_matrix subroutine with that written outside of a module. We no longer have to explicitly pass the matrix dimensions and can instead take advantage of assumed-shape arguments since the module will generate the required explicit interface for us. This results in a much simpler subroutine interface.

Чтобы подключить модуль внутри программы, используется оператор use:

program use_mod
  use my_mod
  implicit none

  real :: mat(10, 10)

  mat(:,:) = public_var

  call print_matrix(mat)

end program use_mod

Пример: явный список импорта

use my_mod, only: public_var

Пример: импорт по псевдониму

use my_mod, only: printMat=>print_matrix

Каждый модуль должен быть написан в отдельном файле исходного кода .f90. Модули должны быть скомпилированы до компиляции любых программных блоков, которые их подключают оператором use.

Optional arguments#

An advantage of placing subroutines and functions in modules is that they can have optional arguments. In a procedure with an argument declared optional, the present function is used to test if the argument was set in the caller. Optional arguments that are not present may not be accessed within the procedure. Here is a generalization of the vector_norm function that can use powers other than 2 to compute the Lp norm.

module norm_mod
  implicit none
  contains
  function vector_norm(vec,p) result(norm)
    real, intent(in) :: vec(:)
    integer, intent(in), optional :: p ! power
    real :: norm
    if (present(p)) then ! compute Lp norm
      norm = sum(abs(vec)**p) ** (1.0/p)
    else ! compute L2 norm
      norm = sqrt(sum(vec**2))
    end if
  end function vector_norm
end module norm_mod

program run_fcn
  use norm_mod
  implicit none

  real :: v(9)

  v(:) = 9

  print *, 'Vector norm = ', vector_norm(v), vector_norm(v,2)
  print *, 'L1 norm = ', vector_norm(v,1)

end program run_fcn