To: J3 J3/20-107 From: Milan Curcic, Jeremie Vandenplas, Zach Jibben Subject: Default value for optional arguments Date: 2020-February-23 Reference: 18-136r1, 18-122r1 1. Introduction This paper contains a proposal for Fortran 202y, to allow a programmer to specify a default value for optional dummy arguments. This would allow the programmer to then safely reference such variables in expressions regardless of whether the actual argument is present or not. 2. Problem Currently, standard Fortran does not allow setting a default value for optional arguments. Default value of an optional argument is the value that the dummy argument would take if the corresponding actual argument is not present. If declaring a dummy argument as optional, the user must: * Explicitly test for the presence of the actual argument using the intrinsic function present(); * Use a separate variable inside the procedure to assign the value because the optional dummy argument that is not present must not be referenced in expressions other than as actual argument to the intrinsic function present(). This example function illustrates the problem: real function quadratic(x, a, b, c) ! returns a + b * x + c * x**2 if c is present ! and a + b * x otherwise real, intent(in) :: x, a, b real, intent(in), optional :: c real :: c_tmp ! use another var. to reference the missing arg c_tmp = 0 ! default value if c is not present if (present(c)) c_tmp = c quadratic = a + b * x + c_tmp * x**2 end function quadratic For any dummy argument with the optional attribute, the programmer must use the intrinsic function present() to check for the presence of the argument. Furthermore, if the optional dummy argument is meant to be used in multiple places in the procedure, the programmer is likely to use the pattern from the example above, where a "temporary" variable is declared and used in place of the dummy argument, which disconnects the implementation from the user interface. Furthermore, this requires at least 3 lines of code (declaration of c_tmp, initialization of c_tmp, and testing for the presence of c) only to handle the scenario of a missing optional argument. This proposal seeks to address the issue that explicitly checking for presence of the optional dummy argument and using a helper variable is cumbersome and error-prone. The primary benefit of this feature is the reduction in source code needed to handle optional arguments. This benefit is even greater in scenarios where the optional argument is used in many places in the procedure, and a helper variable is used for its value instead. Reduction in needed source code would result in more readable and more correct programs. The secondary benefit of this is programmer happiness, as working with optional arguments would require less typing. 3. Proposed solution As suggested by Van Snyder in 18-136r1, the problem could be solved by allowing an optional argument to be initialized using a constant expression. The optional argument would then only be initialized if the corresponding actual argument is not provided by the caller. Example: real function quadratic(x, a, b, c) ! returns a + b * x + c * x**2 if c is present ! and a + b * x otherwise real, intent(in) :: x, a, b real, intent(in), optional :: c = 0 quadratic = a + b * x + c * x**2 end function quadratic In this snippet, we use the assignment operator (=) to specify the default value of the optional dummy argument. Like initializer, the optional argument can be assigned any constant expression, as defined in Section 10.1.12 of 18-007r1. While there may be concerns that the same syntax is used to implicitly set the save attribute for variables in procedures, there is no conflict because the language prohibits dummy arguments from having an initializer, or the save attribute. The change to the standard to allow this feature would thus be to allow an optional dummy argument to have an initializer, which would be triggered only when corresponding actual argument is not passed by the caller. This improvement has already been suggested by Van Snyder in 18-136r1, has received votes in the user survey for 202X (see isotc.iso.org/livelink/livelink?func=ll&objId=19530634&objAction=Open), and appeared on Data subgroup's wishlist at meeting 215 (see 18-122r1). 4. Backward compatibility This addition to the language would not break any existing standard conforming Fortran program, and thus preserves Fortran's backward compatibility. 5. Related behavior 5.1. present() There are two possible behaviors of the function present() when an initializer is provided. The first behavior would be that the function present() always returns .true. when the optional dummy argument has an initializer, and independently of the fact that an actual argument is passed or not by the caller. Therefore, with such a behavior, the function present() becomes useless for optional arguments with a default value. Example: real function foo(a, b) real, intent(in), optional :: a real, intent(in), optional :: b = 0 print*, present(a) !.true. if an actual argument is provided !by the caller, .false. otherwise print*, present(b) !always .true. due to the initializer end function foo The second behavior would be that the function present() returns .true. or .false. when an actual argument is passed or not by the caller, and independently of the fact that the optional dummy argument has, or hasn't an initializer. A use case of this behavior of the function present() could be: real function foo(a, b) real, intent(in) :: a real, intent(in), optional :: b = 1.234 if (present(b)) then ... !expensive input validation here end if ... end function foo 5.2. intent(out) There are several different possible behaviors for intent(out) variables. One option is to disallow default values on optional intent(out) variables. However, there are use cases where it is desirable to return the value of a variable if a caller requests it, without requiring the added logic of present() and "temporary" variables described above. Alternatively, we might treat intent(in) and intent(out) variables identically, so that the following would be valid: real function foo(a) real, intent(out), optional :: a = 0 end function foo The benefit is that, just like proposed for intent(in) variables, intent(out) variables would be usable regardless of whether the value is actually passed back to the caller, removing the need for present() and "temporary" variables. The complication is that a present intent(out) variable is initially undefined, meaning the logic of present() might still be necessary in this case. Furthermore, the behavior suggested above where present() = .true. always for variables with a specified default would not be desired for intent(out) variables. A possible use case for this behavior could be: integer function open(filename, mode, iostat) result(u) character(*), intent(in) :: filename character(*), intent(in), optional :: mode integer, intent(out), optional :: iostat = 0 character(3) :: mode_ character(:),allocatable :: action_, position_, status_, access_ character(:),allocatable :: form_ !some code !.... open(newunit=u, file=filename, & action = action_, position = position_, status = status_, & access = access_, form = form_, & iostat = iostat) end function A third option is that optional intent(out) variables with a specified default are always initialized to that default, even when present, rather than leaving them undefined. 5.3 intent(inout) Dummy arguments with intent(inout) or no specified intent follow from the behavior for intent(in) and intent(out). If an optional intent(inout) variable is present, the existing behavior is followed. The dummy variable has the value of the actual argument on invocation of the procedure, its data may be used or manipulated during the scope of the procedure, then it is returned to the scoping unit. If an actual argument is not provided for an optional dummy argument with a specified default and intent(inout), the variable recieves the default value and may be used or modified throughout the procedure. The data is then not returned to the scoping unit, since no actual argument was provided. This would allow the following routine: subroutine foo(x) integer, intent(inout), optional :: x = 0 ! do stuff that could use or assign to x end subroutine foo This would be equivalent to: subroutine foo(x) integer, intent(inout), optional :: x integer :: x_ if (present(x)) then x_ = x else x_ = 0 end if ! do stuff that could use or assign to x_ if (present(x)) x = x_ end subroutine foo 5.4 Arrays Default values for array dummy arguments present a difficulty, particularly expressions involving a scalar. Some decision must be made for the behavior in the following instances: real, intent(in), optional :: a(:) = 0 real, intent(inout), optional :: b(:) = 0 real, intent(inout), allocatable, optional :: c(:) = 0 In each case, the size of the array is not determined if an actual argument is not provided. One option is of course to forbid defaults for arrays. This isn't preferable, but allowing defaults for scalars only may still address the majority of use cases. Another option is to forbid array broadcasting as a default specifier. That is, allow only defaults such as [0,0] for arrays, where the length is clear. 6. Further discussion Online discussion that led to this proposal can be found at https://github.com/j3-fortran/fortran_proposals/issue/22.