Closed jvdp1 closed 4 years ago
The changes in the PR are fine. Regarding the question in the description, the argument is intent(out)
. One option is to not allow default values for intent(out)
arguments. The other option is to make present(value)
to return .true.
or .false.
depending on if the value is actually present. So it would work differently than intent(in)
arguments. Good question --- we need to clarify that in the proposal.
The other option is to make
present(value)
to return.true.
or.false.
depending on if the value is actually present. So it would work differently thanintent(in)
arguments.
I think it should work in the same way for both intent(in)
and intent(out)
. Otherwise, what would be its behaviour for intent(inout)
?
@sblionel also supported this interpretation of present
in an earlier comment (https://github.com/j3-fortran/fortran_proposals/pull/137#issuecomment-573321660).
I'd argue for the opposite:
The other option is to make present(value) to return .true. or .false. depending on if the value is actually present.
This makes more sense to me. I always thought of present()
as a test for "was the actual argument passed by the caller?"
There's also a technical argument:
present()
always returns .true.
for an optional argument with a default value, then the function is useless for such optional arguments;present()
returns .false.
for an optional argument with a default value doesn't have a corresponding actual argument, it's still useful. For example, you may want to check if the argument was passed by the caller so you can validate or sanitize the input:subroutine simulate(a, b)
real, intent(in) :: a
real, intent(in), optional :: b = 1.234
if (present(b)) then
... ! expensive input validation here
end if
...
end subroutine simulate
In this example, expensive input validation code would execute only if argument passed by the caller, which is otherwise not needed.
In summary, present()
should return .false.
if there's no corresponding actual argument, regardless of whether the optional argument has a default value.
From #137:
I will also need help suggest a specific and formal addition/change to the standard.
I am not sure what you mean.
I mean, let's also include the text to be included in the standard, should this proposal be accepted. Which section of the standard should be edited and how? Should any text be removed? What should be added?
Although this may be in the scope of the Committee, the more we can help the easier will a proposal be accepted and implemented. Let's help as much as we can and know.
I don't think a default value for an optional, intent(out)
argument makes any sense. intent(out)
arguments are undefined until they become defined within the body of the procedure. So what code would you write that assumed the argument had a value before you assigned one to it? I think the only use case for such a thing to be allowed would be poor code design.
But this does bring up the question of optional, intent(inout)
. Can you assign to it if no actual argument is not present? What about optional
with no intent specified?
It would be my opinion that default values for optional arguments should only be allowed for intent(in)
arguments unless and until somebody can come up with both a coherent use case and implementation strategy for the other possibilities. This would allow the possibility of the naive but simple implementation strategy of just treating default values as syntactic sugar like the following:
function something(arg)
integer, intent(in), optional :: arg = 1
integer :: something
! function body
end function something
would be equivalent to
function something(arg)
integer, intent(in), optional :: arg
integer :: something
integer :: arg_
if (present(arg)) then
arg_ = arg
else
arg_ = 1
end if
! replace all occurrences of arg with arg_ in the body
end function something
That simple transformation doesn't make any sense, and in fact causes a run time error for any other intent.
I would be in favor of still allowing present
to return .false.
for default values, as has already been explained to have a use case. I personally probably wouldn't use it that way, but it's not unreasonable to want to.
I would be in favor of still allowing
present
to return.false.
for default values, as has already been explained to have a use case. I personally probably wouldn't use it that way, but it's not unreasonable to want to.
I was in favor of such a behaviour of present
too, but I tried to integrate @sblionel 's comment (see #137). Such a behavior (.false. or .true. independently of the initializer) would be also in agreement with the function optval
implemented in stdlib
.
It seems that most people would be in favor of present
returning .false./.true. indepentely of the initializer. @milancurcic: I can modify my PR in that sense, and add your use case for present
as an example.
@sblionel what would be the issue of such a behavior (in addition to its implementation in a compiler)?
Let's discuss pros and cons of both alternatives in the proposal. This will surely come up during a discussion at the committee, no matter which alternative we choose as the better one.
On Tue, Jan 14, 2020, at 12:55 AM, Jeremie Vandenplas wrote:
I would be in favor of still allowing
present
to return.false.
for default values, as has already been explained to have a use case. I personally probably wouldn't use it that way, but it's not unreasonable to want to.I was in favor of such a behaviour of
present
too, but I tried to integrate @sblionel https://github.com/sblionel 's comment (see #137 https://github.com/j3-fortran/fortran_proposals/pull/137). Such a behavior (.false. or .true. independently of the initializer) would be also in agreement with the functionoptval
https://github.com/fortran-lang/stdlib/blob/f300f4a609ab02620b82ee2c79566361d84505c4/src/stdlib_experimental_optval.f90#L37 implemented instdlib
.It seems that most people would be in favor of
present
returning .false./.true. indepentely of the initializer. @milancurcic https://github.com/milancurcic: I can modify my PR in that sense, and add your use case as an example. @sblionel https://github.com/sblionel what would be the issue of such a behavior (in addition to its implementation in a compiler)?— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/j3-fortran/fortran_proposals/pull/140?email_source=notifications&email_token=AAAFAWA4HS3BDT4YWB7FJZTQ5VVYLA5CNFSM4KGJMFM2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEI3U76I#issuecomment-574050297, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAFAWGRUZMM2BQIM4ZC3GTQ5VVYLANCNFSM4KGJMFMQ.
I don't think a default value for an optional, intent(out) argument makes any sense. intent(out) arguments are undefined until they become defined within the body of the procedure. So what code would you write that assumed the argument had a value before you assigned one to it? I think the only use case for such a thing to be allowed would be poor code design.
For intent(out)
, I agree my first example might be useless. However, what about the following use case (from stdlib
open
):
integer function open(filename, mode, iostat) result(u)
character(*), intent(in) :: filename
character(*), intent(in), optional :: mode
integer, intent(out), optional :: iostat
character(3) :: mode_
character(:),allocatable :: action_, position_, status_, access_, form_
!some code
!....
if (present(iostat)) then
open(newunit=u, file=filename, &
action = action_, position = position_, status = status_, &
access = access_, form = form_, &
iostat = iostat)
else
open(newunit=u, file=filename, &
action = action_, position = position_, status = status_, &
access = access_, form = form_)
end if
end function
which would become:
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_, form_
!some code
!....
open(newunit=u, file=filename, &
action = action_, position = position_, status = status_, &
access = access_, form = form_, &
iostat = iostat)
end function
In this use case, the function present
is not used, but the initializer allows to avoid a if
statement, which simplifies the code.
For intent(inout)
, a use case similar to the one of @milancurcic could be:
subroutine simulate(a, b)
real, intent(in) :: a
real, intent(inout), optional :: b = 1.234
if (present(b)) then
... ! expensive input validation here
end if
!some computations with b
if (present(b)) then
!some expensive computations that modify b and return the newly computed b
end if
end subroutine simulate
While this would probably be a poor design, it would be possible if default values are allowed for intent(in)
, intent(out)
, and intent(inout)
.
Let's discuss pros and cons of both alternatives in the proposal. This will surely come up during a discussion at the committee, no matter which alternative we choose as the better one.
@certik I will push another version of a description of present
.
This is very interesting development. At first I wasn't considering intent(out)
or intent(in out)
scenarios at all because I don't use them much (but they must be considered, of course). After reading Brad's comment here, I also struggled thinking of a good use case for a default value of an intent(out)
until I saw Jeremie's example with the open()
function. A more general form of this example could be the use case of return codes for exception handling (many many projects use this style of exception handling):
subroutine simulate(a, rc)
real, intent(in) :: a
integer, intent(out), optional :: rc = 0 ! assume success
... ! simulation code
if (present(rc)) then
! test for simulation state
if (some_erroneous_condition) then
rc = 173 ! meaningful error code
return
end if
end if
... ! more simulation code
end subroutine simulate
The only aspect that the default initializer helps here with is that we don't have to have a separate line at the top: rc = 0 ! assume success
. So it seems to me that in the case of intent(out)
, setting default value really only acts as an initializer (which doesn't imply save!), which is minor, but can still be useful.
Another technical argument for allowing default value for any intent
is that the rule is easier to explain, write, and implement (no special cases).
Thanks everybody for brainstorming this. This is exactly the kind of work that needs to happen between Committee meetings that we can all do very efficiently at GitHub. So that by the time the Committee meets, the proposal is truly ready with all these "details" figured out.
If there's not a technical reason for disallowing a default value for optional intent(out) arguments then I say it should be allowed. I can't think of a really compelling use case that needs it either, but we shouldn't be trying to anticipate what someone might find useful in this context. And I agree with @milancurcic it will be easier to write the specification if we don't add unnecessary constraints.
@milancurcic , just for clarity, your suggestion is that a default value for intent(out)
would imply something like the following, but you would still need to check if present
before any assignment statements?
subroutine foo(stat)
integer, intent(out), optional :: stat = 0
! do stuff
if (some_error) then
if (present(stat)) stat = some_error_code
return
end if
end subroutine foo
would be equivalent to
subroutine foo(stat)
integer, intent(out), optional :: stat
if (present(stat)) stat = 0
! do stuff
if (some_error) then
if (present(stat)) stat = some_error_code
return
end if
end subroutine foo
I would actually suggest something a bit different, which would then be coherent when combined for intent(inout)
, and preclude the necessity of checking for present
everywhere.
subroutine foo(stat)
integer, intent(out), optional :: stat = 0
! do stuff
if (some_error) then
stat = some_error_code
return
end if
end subroutine foo
would be equivalent to
subroutine foo(stat)
integer, intent(out), optional :: stat
integer :: stat_
stat_ = 0
! do stuff
if (some_error) then
stat_ = some_error_code
if (present(stat)) stat = stat_
return
end if
if (present(stat)) stat = stat_
end subroutine foo
Of course the actual implementation of a compiler would be smarter than having to insert that extra line before every return statement (one would hope).
But this makes the following work as well.
subroutine foo(x)
integer, intent(inout), optional :: x = 0
! do stuff that could use or assign to x
end subroutine foo
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
And it would be exactly the same transformation if no intent is specified. This at least gives the feature a coherent idea that if you have a default value for an optional argument, you no longer need to check if it's present, no matter what it's intent is. But as has been mentioned, you might still want the ability to check if it's present, which my simple transformation would preclude.
And it would be exactly the same transformation if no intent is specified. This at least gives the feature a coherent idea that if you have a default value for an optional argument, you no longer need to check if it's present, no matter what it's intent is. But as has been mentioned, you might still want the ability to check if it's present, which my simple transformation would preclude.
Did I understand you correctly, @everythingfunctional:
The optional argument with an initializer will behave as a declared variable, with an initial value equal to the default value (initializer) (so, no need of present()
to use the dummy argument). Also,
inten(in)
and the caller provides an actual argument, it wil get the value of the actual argument; (Use case: see proposal)intent(out)
and the caller provides an actual argument, the returned value is the default value if the optional argument is not modified, or the assigned value otherwise; (Use case: see @jvdp1 'open' scenario; @everythingfunctional 's example)intent(inout)
and the caller provides an actual argument, it wil get the value of the actual argument, AND the returned value is equal to the actual value if it is not modified, or the assigned value otherwise. (Use case: @everythingfunctional 's example).present()
can still be used in all cases (assuming it has the same behaviour as in the current standard; see the differen comments). (Weak?) use cases for the three scenarios are provided in this PR.
@jvdp1 , yes, those statements are true as I envisioned it.
This at least gives the feature a coherent idea that if you have a default value for an optional argument, you no longer need to check if it's present, no matter what it's intent is.
Exactly! While this was implied in the proposal, we didn't state it explicitly and we should: Using default value for an optional argument allows the programmer to safely reference it in expressions regardless of intent and whether the actual argument is present or not.
Looks like there is agreement on this, so I am going to merge it.
@sblionel what would be the issue of such a behavior (in addition to its implementation in a compiler)?
I missed seeing this earlier.
If one was to allow present()
to be useful for a default argument, then the called routine would need to add prologue code to:
present()
tested the passed argument and not the tempIf you also allowed this for other than intent(in)
, then there would need to be epilogue code to copy the temp back to the dummy, if supplied.
This interpretation would also complicate passing the argument to another routine. At present (!), "presentness" is passed on to other routines where the dummy is optional
.
To me, this is awfully complicated and performance-inhibiting. I foresee a lot of opposition to such a thing. I still favor default values being supplied by the caller, in which case everything falls out without adding a layer of complexity. It is also far easier to explain (and implement.)
In my experience, I have not seen default values combined with "did the caller really pass something?" This is not to say that there couldn't be a case for that, but it isn't compelling to me.
@jvdp1 if you have time, would you mind capturing the rest of the discussion here, such a Steve's comment above, and send a new PR?
@sblionel Thanks! Indeed, I didn't consider implementation it all, nor do I have experience with it. Your argument makes sense to me.
I just added an example on how I think
present
may behave when an initializer is mentioned or not.I am just wondering what would happen in such a case:
What will happen with
foo
when it is called without providing an actual argument, sincepresent()
always returns.true.
. Probably it should not be a problem since value is already used.