8

В fortran мы можем определить аргументы по умолчанию. Однако, если необязательный аргумент отсутствует, его также нельзя установить. При использовании аргументов в качестве ключевых аргументов со значениями по умолчанию, это приводит к громоздким конструктов какFortran 2003/2008: Элегантные аргументы по умолчанию?

PROGRAM PDEFAULT 

    CALL SUB 
    CALL SUB(3) 

CONTAINS 
    SUBROUTINE SUB(VAL) 
    INTEGER, OPTIONAL :: VAL 
    INTEGER :: AVAL ! short for "actual val" 

    IF(PRESENT(VAL)) THEN 
     AVAL = VAL 
    ELSE 
     AVAL = -1 ! default value 
    END IF 

    WRITE(*,'("AVAL is ", I0)') AVAL 
    END SUBROUTINE SUB 

END PROGRAM PDEFAULT 

Лично я часто столкнулся с проблемой accidentially набрав VAL вместо AVAL, то есть разрыв между именем переменной в интерфейсе, и инициализированное значение, используемое в коде, может привести к ошибкам во время выполнения, не говоря уже о том, что этот способ инициализации довольно многословный.

Есть ли еще более элегантный способ использования необязательных аргументов со значением по умолчанию?

Пример Было бы чувствовать себя более естественно, чтобы написать что-то вроде

IF(NOT(PRESENT(VAL))) VAL = -1 

, потому что он избегает VAL против AVAL путаницы. Но это неверно, по-видимому, потому, что Fortran передает аргументы по ссылке и, следовательно, если VAL не присутствует в операторе CALL, то память не связана с VAL, а VAL = -1 вызовет segfault.

ответ

4

Вы описали ситуацию довольно хорошо. Другого пути я не знаю (получается, что я действительно не знал, см. Ответ Гейба). Образец с локальной переменной, названной так же, - это то, что люди часто используют. Другой вариант - просто поставить if (present()) else всюду, но это неудобно.

Дело в том, что они являются необязательными аргументы, а не по умолчанию аргументы. У Fortran нет аргументов по умолчанию. Возможно, это было лучше, но это не то, что члены комитета выбрали в 80-х годах при подготовке Fortran 90.

1

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

MODULE subs 
    implicit none 
    public :: sub 

    interface sub 
    module procedure sub_default 
    module procedure sub_arg 
    end interface 
contains 
    SUBROUTINE SUB_arg(VAL) 
    INTEGER :: VAL 
    WRITE(*,'("VAL is ", I0)') VAL 
    END SUBROUTINE SUB_arg 

    SUBROUTINE SUB_default 
    integer, parameter :: default = 3 
    CALL SUB_arg(default) 
    END SUBROUTINE SUB_default 
END MODULE SUBS 

PROGRAM test 
    use subs, only: sub 
    call sub 
    call sub(5) 
END PROGRAM TEST 

Опять же, я не рекомендую этот подход, но я думал, что я должен включить это в любом случае в качестве альтернативного способа предоставления то, что выглядит как по умолчанию.

1

Я надеюсь, Fortran поддерживать популярный синтаксис как

subroutine mysub(x, val = -1) 
integer, optional :: val 

или в более Fortran стиле

subroutine mysub(x, val) 
integer, optional :: val = -1  !! not SAVE attribute intended 

, но это, похоже, не поддерживается (по состоянию на 2016 г.). Поэтому некоторые способы решения проблемы должны выполняться пользователями ...

В моем случае, после проб и ошибок, я устроился на прикрепление один подчеркивания к факультативному фиктивному аргументу, так что делать что-то вроде (*)

subroutine mysub(x, val_) 
integer, optional :: val_ 
integer val 

Других люди, кажется, как обратную картину (т. е. фиктивная переменная =>sep, локальная переменная =>sep_, см., например, split() в StringiFor). Как видно из this line, самый короткий путь, чтобы установить значение по умолчанию является

val = -1 ; if (present(val_)) val = val_ 

Но поскольку даже эта линия несколько многословен, я обычно определяют макрос как

#define optval(x,opt,val) x = val; if (present(opt)) x = opt 

в общем файле заголовка и использования это, как

subroutine mysub(x, val_, eps_) 
    integer :: x 
    integer, optional :: val_ 
    real, optional :: eps_ 
    integer val 
    real  eps 
    optval(val, val_, -1) 
    optval(eps, eps_, 1.0e-5)  

    print *, "x=", x, "val=", val, "eps=", eps 
endsubroutine 

... 
call mysub(100) 
call mysub(100, val_= 3) 
call mysub(100, val_= 3, eps_= 1.0e-8) 

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


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

#define getkey(T) type(T), optional :: key_; type(T) key; if (present(key_)) key = key_ 

module mymod 
    implicit none 

    type mysub_k 
     integer :: val = -1 
     real  :: eps = 1.0e-3 
    endtype 
contains 

subroutine mysub(x, seed_, key_) 
    integer :: x 
    integer, optional :: seed_ 
    integer :: seed 
    getkey(mysub_k) !! for all the remaining keyword arguments 
    optval(seed, seed_, 100)  

    print *, x, seed, key% val, key% eps 
endsubroutine 

endmodule 

program main 
    use mymod, key => mysub_k 

    call mysub(10) 
    call mysub(20, key_= key(val = 3)) 
    call mysub(30, seed_=200, key_= key(eps = 1.0e-8)) ! ugly... 
endprogram 

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


(*) Я знаю, что часто считаются уродливыми для использования макросов CPP, но IMO зависит от того, как они используются; если они ограничены ограниченным расширением синтаксиса Fortran, я считаю, что разумно использовать (поскольку в Fortran нет объекта метапрограммирования); с другой стороны, вероятно, следует избегать определения зависимых от программы констант или ветвей. Кроме того, я предполагаю, что это будет более мощным, чтобы использовать Python и т.д., чтобы сделать более гибкий препроцессор (например, PreForM.py и fypp и так далее), например, чтобы синтаксис как subroutine sub(val = -1)

+0

Первое решение - это то, что я опубликовал в исходном вопросе - изменение соглашения об именах на самом деле не делает его новым шаблоном. Второе решение - печально недопустимый код, если имеется более одного необязательного аргумента, поскольку fortran не позволяет смешивать объявление и код выполнения - в то время как для одного необязательного аргумента использование макроса, вероятно, недостаточно для того, чтобы оправдать безвестность , – kdb

+0

Привет, потому что нынешние стандарты Fortran не позволяют никакого реального «решения» (как было предложено Владимиром ниже), нет другого способа, кроме как использовать локальную переменную с похожим именем, и я думал, что вы ищете какой-то систематический (менее подверженные ошибкам) ​​обходные пути. RE вторая точка, приведенный выше код действителен (вы можете попробовать), и вы также можете добавить другие обычные необязательные переменные перед ключом. Дело в том, что setkey() должен появиться после всех других объявлений, но это естественно, потому что этот тип аргументов ключевого слова приходит после всех необязательных аргументов. – roygvib

+0

Кроме того, моя цель - просто представить мою практику, никогда не защищать ее использование. Что еще более важно, мое намерение состоит в том, чтобы показать, как текущий синтаксис Fortran ограничен в некоторых частях и принуждает пользователя писать довольно подробный/длинный код. – roygvib

6

Хотя и глядя в это, я узнал, что вы действительно можете сделать что-то вроде предлагаемого примера, используя атрибуты OPTIONAL и VALUE (по крайней мере, с gfortran, не знаете, как могут обрабатывать различные компиляторы). Например:

PROGRAM PDEFAULT 

    CALL SUB 
    CALL SUB(3) 

CONTAINS 
    SUBROUTINE SUB(VAL) 
    INTEGER, OPTIONAL,VALUE :: VAL 

    IF(.NOT. PRESENT(VAL)) VAL = -1 ! default value 

    WRITE(*,'("VAL is ", I0)') VAL 
    END SUBROUTINE SUB 

END PROGRAM PDEFAULT 

Это было реализовано в версии 4.9 gfortran. И вот соответствующее разъяснение в documentation for argument passing conventions:

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

Я также нашел this discussion интересный как исторический контекст.

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

Обратите внимание, что это поведение не является частью стандарта Fortran и зависит от реализации данного компилятора. Например, пример кода segfaults при использовании ifort (версия 16.0.2).

+0

Wow, good Нашел, я понятия не имел об этом! –

+0

I _believe_ это неверно. Fortran (что не означает, что он не будет делать то, что на практике нужно использовать с некоторыми компиляторами/runtimes). Если 'val' нет, то ссылаться/определять его не разрешается. Это оператор 'if'. Ограничения, заданные стандартом на использование не существующих фиктивных аргументов, не упоминают атрибут 'value'. «Определяемая анонимная копия» применяется только в случае настоящего фиктивного аргумента с атрибутом «значение». [Надеюсь, кто-то может показать, что я ошибаюсь.] – francescalus

+0

Хм, я думаю, вы правы. Утром я волновался, что на короткое время, чем поток багзиллы убедил меня. Однако нигде на этой странице они не предполагают, что ссылка на аргумент разрешена, если она отсутствует. –

Смежные вопросы