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
因为它避免了 VAL
与 AVAL
的混淆。但它是无效的,大概是因为 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 这样的语法=]
虽然也在调查这个问题,但我发现您实际上可以使用 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
这是在 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
。)
在堡运行中,我们可以定义默认参数。但是,如果不存在可选参数,则也不能设置它。当使用参数作为具有默认值的关键字参数时,这会导致笨拙的结构,例如
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
因为它避免了 VAL
与 AVAL
的混淆。但它是无效的,大概是因为 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 这样的语法=]
虽然也在调查这个问题,但我发现您实际上可以使用 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
这是在 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
。)