Fortran 2003 / 2008:优雅的默认参数?

Fortran 2003 / 2008: Elegant default arguments?

在堡运行中,我们可以定义默认参数。但是,如果不存在可选参数,则也不能设置它。当使用参数作为具有默认值的关键字参数时,这会导致笨拙的结构,例如

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

就我个人而言,我经常运行陷入不小心键入VAL而不是AVAL的问题,即接口中的变量名与使用的初始化值之间的脱节代码可能会引入运行时错误——更不用说这种初始化方式相当冗长了。

是否有更优雅的方式使用具有默认值的可选参数?

示例 写成

这样的感觉更自然
IF(NOT(PRESENT(VAL))) VAL = -1 

因为它避免了 VALAVAL 的混淆。但它是无效的,大概是因为 Fort运行 通过引用传递参数,因此如果 VAL 不存在于 CALL 语句中,则没有内存与 VAL 关联并且VAL = -1 会导致段错误。

你描述的很好。没有其他我知道的方法,这是符合标准的。局部变量命名相似的模式是人们经常使用的。另一种选择是到处都放 if (present()) else,但这很尴尬。

重点是它们是可选参数,而不是默认参数。 Fortran 没有默认参数。可能更好,但这不是委员会成员在 80 年代准备 Fortran 90 时选择的。

虽然我当然不会提倡在大多数情况下这样做(实际上在某些情况下你不能这样做),但有时可能会使用一个接口为具有不同 [=15= 的多个例程提供单个入口点]required 个参数,而不是使用可选参数。例如你的代码可以写成

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

同样,我不推荐这种方法,但我认为无论如何我都应该将其作为提供看起来像默认值的东西的替代方法包括在内。

我希望 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() in StringiFor, for example). As seen in 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 )

但是,我认为这仍然远非优雅,只不过是为了使其更不容易出错(通过在子例程主体中使用所需的变量名)。


非常 "big" 子例程的另一种解决方法可能是传递包含所有剩余关键字参数的派生类型。例如,

#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 and fypp 等)会更强大,例如,允许像 subroutine sub( val = -1 )[=22 这样的语法=]

虽然也在调查这个问题,但我发现您实际上可以使用 OPTIONALVALUE 属性(至少对于 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

这是在 gfortran 4.9 版本中实现的。这里是documentation for argument passing conventions中的相关解释:

For OPTIONAL dummy arguments, an absent argument is denoted by a NULL pointer, except for scalar dummy arguments of type INTEGER, LOGICAL, REAL and COMPLEX which have the VALUE attribute. For those, a hidden Boolean argument (logical(kind=C_bool),value) is used to indicate whether the argument is present.

我还发现 this discussion 作为历史背景很有趣。

也许更有知识的人可能会评论这样做是否是一个坏主意(除了依赖于编译器),但至少从表面上看它似乎是一个不错的解决方法。

请注意,此行为不是 Fortran 标准的一部分,取决于给定编译器的实现。例如,示例代码在使用 ifort(版本 16.0.2)时出现段错误。

另一种可能性是使用关联块,它将局部变量名称与与可选参数同名的变量关联起来,例如

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

ASSOCIATE (VAL => AVAL)
    WRITE(*,'("VAL is ", I0)') VAL
END ASSOCIATE

END SUBROUTINE SUB

不理想,但允许您对参数和例程主体使用相同的变量名。一想到我为应对可选参数缺少默认值而编写的大量不整洁的代码,我就不寒而栗——滚上 F202X。

Fortran 标准库(https://github.com/fortran-lang/stdlib) provides a function called optval that is used in stdlib_logger 例如:

subroutine add_log_file( self, filename, unit, action, position, status, stat )
    ...
    character(*), intent(in), optional :: action
    ...
    character(16)  :: aaction
    ...
    aaction = optval(action, 'write')
    ...
end subroutine add_log_file

所以他们表示“实际”值的方式是前置 a

恕我直言,我喜欢带有附加 _ 的选项,因为可选值在调用签名中以视觉方式标记。

这是一个优雅(即简短、清晰、standard-conforming)的解决方案:

  subroutine sub(val)
    integer, optional :: val

    write(*,'("aval is ", i0)') val_or_default(val, default=-1)
  end subroutine

  integer function val_or_default(val, default)
    integer, optional, intent(in) :: val
    integer, intent(in) :: default

    if (present(val)) then  ! False if `val` is is not present in `sub`.
      val_or_default = val
    else
      val_or_default = default
    endif
  end function

这利用了可选参数仍然可以传递给函数的事实,即使它们不存在, 只要相应的伪参数也是可选的。

GitHub 上至少有一个 val_or_default 的通用实现 对于所有内部数据类型。 (他们称之为 optval。)