В конце 2023 года был принят очередной стандарт языка Фортран, ISO/IEC 1539-1:2023. Programming languages. Fortran (в просторечии – Fortran 2023).

Отличия стандарта 2023 года от действовавшего до него стандарта Fortran 2018 полностью описаны в свободно доступном документе The new features of Fortran 2023, не имеющем официального статуса.

Приведём короткий обзор нововведений.

Максимальная длина строки программы увеличена до 10 тысяч символов, максимальная длина одного оператора – до миллиона символов. Компиляторам предписывается в точности соблюдать эти лимиты, не более и не менее. Как утверждается, целью является облегчение написания программ искусственными интеллектами.

Разрешено автоматическое размещение в памяти строк переменной длины в результате получения выходных параметров системных процедур и операторов (т.е. пользователю не надо руками отводить буфер).

Новые атрибуты typeof и classof для создания переменных такого же типа, как другие:

integer :: i
typeof (i) :: j


Долго ожидавшиеся условные выражения:

value = ( a>0.0 ? a : 0.0)

Скобки обязательны, но одно выражение может содержать вложенные условия:

value = ( a>0.0 ? a : b > 0.0 ? b : 0.0)

Также результатом условного выражения может быть один или несколько фактических параметров процедуры, в том числе передаваемых по ссылке или опускаемых необязательных:

call sub ( ( x>0? x : y>0? y : z ), ( edge>0? edge : mode==3? 1.0 : .nil.) )

Лексема .nil. здесь передаёт отсутствие фактического параметра.

Более расширенное использование двоичных, восьмеричных и шестнадцатеричных (boz) литеральных констант. В языке Фортран такие литералы не имеют типа, представляя собой просто запись содержимого ячеек памяти, поэтому их использование отличается от использования десятичных чисел. В целом, теперь, когда можно вывести тип значения по левой части присваивания, то в правой могут использоваться boz.

Добавлены полезные подпрограммы токенизации строк split и tokenize для эффективной работы со словами в строке.

Через 59 лет после реализации аналогичного решения в языке PL/I, добавлены тригонометрические функции для работы с аргументами в градусах (acosd вдобавок к acos и так далее до tand).

Добавлены тригонометрические функции для работы с аргументами в интервале от 0 до Пи в соответствии со стандартом IEEE (от acospi до tanpi).

Функция selected_logical_kind позволяет получить атрибут байтовой длины логического значения, в которое уместится заданная разрядность.

Ограничены типы параметров стандартной подпрограммы system_clock. В частности, разрядность должна быть не меньше умолчательного размера integer.

Добавлены новые функции педантичных максимумов и минимумов для соответствия новому стандарту IEEE, ISO/IEC 60559:2020 – ieee_max, ieee_max_mag, ieee_min, ieee_min_mag. Отличаются от обычных точной спецификацией, что возвращается в случае различных NaN'ов, положительных и отрицательных нулей и т.п.

Добавлены целые константы logical8, logical16, logical32,
logical64, real16 для указания размеров соответствующих типов. Заметим, что во всех вменяемых компиляторах они равны 8, 16, 32, 64 и 16 соответственно. Шиза косит наши ряды.

Расширено взаимодействие с функциями на языке Си всякими экзотическими случаями вроде многобайтовой кодировки символов по умолчанию.

Добавлены следующие два крайне полезных нововведения в форматном выводе, которых (во всяком случае, второго) ждали чуть ли не 70 лет.

Новый формат вывода AT действует как A с применением функции trim, то есть отбрасывает лишние хвостовые пробелы:

print "(AT,AT)", "Imperial   ", "bastion"

Imperialbastion

Наконец!!! можно управлять печатью нуля перед десятичной точкой в вещественных числах между 0 и 1 (ранее могло быть как .5, так и 0.5 в зависимости от реализации). Для этого предназначены управляющие форматы LZP, LZS, LZ (print/suppress/default), либо ключевой параметр leading_zero=… в операторе open со значениями print, suppress, processor_defined (по некоторым сведениям – и в операторе write):

print "(LZP, F0.2, 1X, LZS, F0.2)", .5, .5

0.50 .50

Расширен синтаксис оператора namelist.

Разрешены динамически размещаемые в памяти объекты, содержащие в себе комассивы, и всё это очень сложно работает.

Добавлен механизм put with notify, обеспечивающий изменение данных в чужом адресном пространстве с отправкой уведомления. Для этого расширен синтаксис оператора комассивного присваивания опцией notify и добавлен оператор notify wait:

use iso_fortran_env 
type(notify_type) nx[*]

me = this_image()
if (me <= 4) then
  x(me)[10, notify=nx] = y
else if (me == 10) then
  notify wait (nx, until_count=4)
  z(1:4) = x(1:4)
end if

Чем это лучше последовательных операторов присваивания и нотификации? Тем, что для двух разных операторов последовательность сообщений об изменении данных и о нотификации может перепутаться в пути от одного узла к другому, и фокус не удастся.

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

Добавлены простые (simple) процедуры. Они отличаются от чистых (pure) процедур тем, что не только не модифицируют внешнее окружение иначе, чем через формальные параметры, но и не читают внешнее окружение иначе, чем через формальные параметры. Очевидно, полезно для выгрузки кода в GPU и в прочих неоднородных архитектурах.

Массивы разрешено индексировать массивами, задавать размерность (количество измерений) массива динамически, и вообще много разврата в стиле языка APL:

A(@[3,5])
! Array element, equivalent to A(3, 5)

A(6, @[3,5], 1) 
! Array element, equivalent to A(6, 3, 5, 1) 

A(@V1, :, @V2) 
! Rank-one array section, the rank of A being
! SIZE (V1) + 1 + SIZE (V2).

integer, dimension(3) :: lb_array = 0 
real :: zz(lb_array+2:)
real, dimension(lb_array:) :: x, y

real, allocatable, dimension(:,:,:) :: x, y, z
integer :: lower(3), upper(3)
allocate(x(:upper), y(lower:upper), z(0:upper))

subroutine ex(a)
real, rank(2) :: a  
! Equivalent to real :: a(:,:)

integer :: x0(10,10,10)
logical, rank(rank(x0)), allocatable :: x1 
! rank 3, deferred shape 
complex, rank(2), pointer :: x2 
! rank 2, deferred-shape
logical, rank(rank(x0)) :: x3 
! rank 3, assumed-shape
real, rank(0) :: x4 
! scalar

Долгожданное введение спецификации редукции в асинхронный оператор цикла, которое раньше было возможно только в макросах OpenMP:

real :: a, b, x(n)
a = 0.
b = -huge(b)
do concurrent (i = 1:n) reduce(+:a) reduce(max:b)
  a = a + x(i)**2
  b = max(b,x(i))
end do

Наконец, много малополезных возможностей, связанных с enum.

В целом, стандарт Fortran 2023 года производит положительное впечатление, а отдельных нововведений прямо-таки заждались. Но при этом вызывает беспокойство, что комитет ISO сильновато ушёл в отрыв от разработчиков компиляторов: предыдущий стандарт Fortran 2018 поддержан компиляторами только в небольшой части, а стандартом де-факто на сегодняшний день остаётся Fortran 2008. Остаётся надеяться, что хотя бы самые востребованные возможности Fortran 2023 будут реализованы достаточно быстро.

Комментарии (16)


  1. Oangai
    16.01.2024 11:37
    +14

    Фортран это всегда круто, как стимпанк и альтернативная история...


    1. WinPooh73
      16.01.2024 11:37
      +1

      Так и представляется реализация Фортрана для механической разностной машины из романа Стерлинга и Гибсона.


    1. Deosis
      16.01.2024 11:37
      +1

      Это уже какой-то ретрокиберпанк, в котором хакеры охотятся за секретной перфокартой с секретным кодом на Фортране.


    1. Fedorkov
      16.01.2024 11:37
      +1

      Или как изучать немецкий после английского.


  1. ImagineTables
    16.01.2024 11:37
    +2

    Выбросы энергии в ноосферу говорят: кто-то опять применил тёмное искусство некромантии!


  1. boulder
    16.01.2024 11:37
    +1

    То есть у языка ФОРмул до 2024 года не было условных выражений?
    Здорово, да :)


    1. mobi
      16.01.2024 11:37
      +2

      Было merge(a, 0.0, a>0.0), а теперь в качестве альтернативы добавили привычный по другим языкам синтаксис (a>0.0 ? a : 0.0).


      1. vadimr Автор
        16.01.2024 11:37

        Не совсем так, merge – обычная функция, а ? - специальная форма. В первом случае вычисляются значения всех трёх аргументов, поэтому такая, например, штука не пройдёт:

        merge(a(i), 0.0, i >= 1)


        1. mobi
          16.01.2024 11:37

          Если функция a объявлена как pure, т.е. не имеет побочных эффектов, то уже в режиме -O1 компилятор gfortran не будет вызывать ее без необходимости:
          https://godbolt.org/z/cKcdMrdxP
          А иначе, да, стандарт требует вычисления всех аргументов функции.
          Но в большинстве случаев gfortran (в отличие от ifort и ifx, как ни парадоксально) очень хорошо оптимизирует merge.


          1. vadimr Автор
            16.01.2024 11:37

            Имелся в виду массив.


            1. mobi
              16.01.2024 11:37

              А, тогда тем более будет оптимизировано.


              1. vadimr Автор
                16.01.2024 11:37
                +1

                program m
                
                  implicit none
                
                  real :: a(2) = 0.
                  integer :: i = 0
                
                  print *, merge(a(i), 0.0, i>=1)
                
                end program m
                gfortran merge.f90 -omerge -O3 -Wl,-ld_classic -ftree-vectorize -march=native -flto -fcheck=bounds
                ./merge
                At line 8 of file merge.f90
                Fortran runtime error: Index '0' of dimension 1 of array 'a' below lower bound of 1
                
                Error termination. Backtrace:
                #0  0x10b38aaae
                #1  0x10b38b7a5
                #2  0x10b38bd15
                #3  0x10aa72eb9
                #4  0x10aa72ede
                #5  0x7ff80bba9385

                Ну и вообще нельзя полагаться на оптимизацию в принципиальном вопросе.


                1. mobi
                  16.01.2024 11:37

                  Ну так вы же специально указали -fcheck=bounds. Без него (просто -O3) у меня gfortran 13.2 выводит
                  0.00000000E+00
                  без каких-либо ошибок.

                  Да и не понимаю я, о чем мы спорим. Я просто привел пример, каким был аналог тернарного условного оператора в предыдущих версиях Fortran.


                  1. vadimr Автор
                    16.01.2024 11:37

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

                    Да и не понимаю я, о чем мы спорим. Я просто привел пример, каким был аналог тернарного условного оператора в предыдущих версиях Fortran.

                    А я уточнил, что это не совсем аналог.


                    1. mobi
                      16.01.2024 11:37

                      Там по ассемблерному коду видно, что компилятор оптимизирует до просто print *, 0.0 и никуда не лезет. И даже в более сложных случаях, если нет побочных эффектов, merge заменяется на сравнение и условный переход, т.е. полностью аналогично тернарному условному оператору.


                      1. vadimr Автор
                        16.01.2024 11:37
                        +1

                        Условная операция, будучи специальной формой, не вычисляет неактивную ветку, это написано в её определении. Любая функция по своей семантике вычисляет все свои входные аргументы, а merge формально является обычной функцией. Иногда оптимизатор может убрать лишнюю работу, но это не гарантированное поведение, и программа не должна от него зависеть. Это важная семантическая разница.

                        В SICP прямо даётся такой пример. Там бесконечную рекурсию так забабахали, заменив условную специальную форму на условную функцию.