j3-fortran / fortran_proposals

Proposals for the Fortran Standard Committee
178 stars 15 forks source link

addition of present() #140

Closed jvdp1 closed 4 years ago

jvdp1 commented 4 years ago

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:

module ...
 ....
contains
subroutine foo(i_,value)
 integer, intent(in) :: i_
 integer, optional, intent(out) :: value = 0
 integer :: value_
 !some computations with value_
 if (present(value)) value = value_
end subroutine foo

end module

program test
 use ...
 implicit none
 call foo(10) !what happens with value in foo if present() always return .true.?
end program

What will happen with foo when it is called without providing an actual argument, since present() always returns .true. . Probably it should not be a problem since value is already used.

certik commented 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.

jvdp1 commented 4 years ago

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.

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)?

milancurcic commented 4 years ago

@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:

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.

milancurcic commented 4 years ago

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.

everythingfunctional commented 4 years ago

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.

jvdp1 commented 4 years ago

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)?

certik commented 4 years ago

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 function optval https://github.com/fortran-lang/stdlib/blob/f300f4a609ab02620b82ee2c79566361d84505c4/src/stdlib_experimental_optval.f90#L37 implemented in stdlib.

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.

jvdp1 commented 4 years ago

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).

jvdp1 commented 4 years ago

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.

milancurcic commented 4 years ago

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).

certik commented 4 years ago

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.

nncarlson commented 4 years ago

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.

everythingfunctional commented 4 years ago

@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.

jvdp1 commented 4 years ago

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,

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.

everythingfunctional commented 4 years ago

@jvdp1 , yes, those statements are true as I envisioned it.

milancurcic commented 4 years ago

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.

certik commented 4 years ago

Looks like there is agreement on this, so I am going to merge it.

sblionel commented 4 years ago

@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:

  1. Test to see if the argument was supplied
  2. Map the name of the dummy to a local temp
  3. Copy either the default or the actual into the temp
  4. Make sure that present() tested the passed argument and not the temp

If 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.

certik commented 4 years ago

@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?

milancurcic commented 4 years ago

@sblionel Thanks! Indeed, I didn't consider implementation it all, nor do I have experience with it. Your argument makes sense to me.