j3-fortran / fortran_proposals

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

Conditional Expressions #183

Open certik opened 3 years ago

certik commented 3 years ago

Relevant papers:

Taking the second example from the 18-274 paper:

  IF (PRESENT(D)) THEN
    CALL SUB(A,B,C,D)
  ELSE IF (X<1) THEN
    CALL SUB(A,B,C,EPSILON(X))
  ELSE
    CALL SUB(A,B,C,SPACING(X))
  END IF

One proposed syntax is "keyword syntax":

CALL SUB(A, B, C, IF (PRESENT(D) THEN D ELSE IF (X < 1) THEN EPSILON(X) ELSE SPACING(X) END IF)

The second proposed syntax is "? syntax":

CALL SUB(A, B, C, ? (PRESENT(D) D :? (X < 1) EPSILON(X) : SPACING(X) ?)
klausler commented 3 years ago

The idea of modifying MERGE was discussed several meetings ago (I can't find which one). I liked the idea, but there were complaints that it would slow down MERGE for everyone and it failed.

That's astonishing and credible at the same time.

urbanjost commented 3 years ago

I use MERGE but hate the name, so how about "CHOOSE", and it would short-circuit and allow character variables of different lengths as arguments. I think the short-circuit would be enough, but I would definitely want it to be able to work with optional parameters so it could be used with

subroutine a(opt)
character(len=*),intent(in),optional :: opt
character(len=:),allocatable :: opt_local
opt_local=choose(lower(opt_local),'default',present(opt))
   ...
so "lower" would not be called on an undefined value.
urbanjost commented 3 years ago

I really do mean it to be different than MERGE; it would return the first or second argument, which could be of different size, not elementally using a mask, but just based on whether the third argument is T or F.

urbanjost commented 3 years ago

And I wish MERGE had been called PICK, given that SELECT is taken.

klausler commented 3 years ago

Whoops, I accidentally closed this issue by hitting the wrong button. Fixing now.

certik commented 3 years ago

Here is the discussion of a new intrinsic, say, ifthen by the committee: https://github.com/j3-fortran/fortran_proposals/issues/183#issuecomment-707843900

certik commented 3 years ago

Finally, here is another paper that we will vote on next Monday:

This is essentially the "arrow form": https://github.com/j3-fortran/fortran_proposals/issues/183#issuecomment-707810787

What is your opinion on that one?

klausler commented 3 years ago

Finally, here is another paper that we will vote on next Monday:

This is essentially the "arrow form": #183 (comment)

What is your opinion on that one?

It still changes the syntax of expressions, so it would affect parsing, AST definitions, &c.; this would be needless work in every implementation over and above the straightforward semantic analysis that a new intrinsic function would cost. Tokenizing the new "->' symbol correctly would impose a look-ahead requirement on tokenization of the current "-". And it implies but does not specify the operator precedence of a "cond-expr" -- does it replace a current parenthesized expression in the syntax? Can it be used as a variable?

I hate it less than the first two ("IF" and the "?") but not by much. I don't understand the aversion to using an intrinsic function; they're easy to parse, they nest in obvious ways, and they're more likely to be understood than new operator syntax.

certik commented 3 years ago

I don't understand the aversion to using an intrinsic function; they're easy to parse, they nest in obvious ways, and they're more likely to be understood than new operator syntax.

The only arguments I've heard against an intrinsic function are summarized here: https://github.com/j3-fortran/fortran_proposals/issues/183#issuecomment-707843900 (harder to do chaining, possibly more confusing due to some arguments not being evaluated).

veryreverie commented 3 years ago

Would this proposal be limited to use in function calls? And to one-line if statements?

I'd like to suggest a slight frame-change, and propose allowing if statements which return values, e.g.

y = if (x>0) then
  x
else
  0
endif

If this syntax was included, then it gives you the power of "conditional arguments", and more besides. Like a lot of these proposals, this doesn't give you something you couldn't already do, but I feel like this is both clearer and more powerful than the existing alternatives, and as previously mentioned can be chained with other things like array initialisation.

I don't believe this would conflict with existing syntax, in that I don't believe you can currently have statements with return values on their own (e.g. the line x on its own is not allowed).

I guess you'd need to decide what to do if when there was no return value because there was no else clause. e.g. in y = if (x<0) then x endif if x>0. Personally I'd favour leaving y however it was before, probably with a compiler warning to suggest adding an else if y is not allocatable or an optional argument.

I suppose also if if statements with return values were allowed, then for consistency the various select case, select type etc. statements should also be allowed to have return values.

For language consistency reasons, I'm against the other syntax options (as much as I personally like the pythonic syntax in python). I can see the arguments for dropping the then and endif parts of the one-line if statement, but I think that if they're required in regular one-line if statements then they should also be required in one-line if statements with return values. I also think the proposed ifthen(x,y,z) function is not ideal, but better than nothing.

For clarity, my ranking of the proposed syntaxes is:

  1. if statements with return values.
  2. The f(a, b, if (x) then y else z endif) syntax.
  3. Some kind of ifthen function.
  4. The original syntax.
  5. [ Big gap ]
  6. everything else.
klausler commented 3 years ago

So you want Fortran parsers to be able to handle statements as parts of expressions, and add a new kind of expression-only statement. What happens if one of your "then" or "else" parts is "returning" the value of a variable named ELSE? or END?

nshaffer commented 3 years ago

I think all the proposed syntaxes are OK (just OK) for simple conditional expressions, but that they become very hard to read for compound conditional expressions. I can only speak for myself, but every example I read with compound conditional expressions I have to mentally step through branch-by-branch to make sure I understand what it's doing. Basically, in reading

call sub(a, b, c, ? (present(d) d :? (x < 1) epsilon(x) : spacing(x) ?)

I mentally reconstruct

block 
  real :: d_
  if (present(d)) then
    d_ = d
  else if (x<1) then
    d_ = epsilon(x)
  else
    d_ = spacing(x)
  end if
  call sub(a,b,c,d_)
end block

If I'm reading someone else's code, I'd much rather see the second form than the first (or any of it's proposed variations). Yes, it's verbose, but it's also obvious.

I'm more partial to a function-like syntax, i.e., ifthen mentioned upthread (or whatever name). The fact that it's clumsy to chain was cited as a con, but to me it is a pro. It should be awkward to write hard-to-read code.

milancurcic commented 3 years ago

We started a poll to collect feedback on this feature. We’ll present the results of the poll to the Committee on Monday when the proposals for this feature are due for discussion and a vote.

veryreverie commented 3 years ago

So you want Fortran parsers to be able to handle statements as parts of expressions, and add a new kind of expression-only statement.

Ideally, yes. I think it would add benefits to the language.

What happens if one of your "then" or "else" parts is "returning" the value of a variable named ELSE? or END?

I'm surprised these are not already reserved words. I guess a possible solution would be to require brackets or similar, so that the syntax would be

y = if (x>0) then
  (x)
else
  (0)
endif

and with an interesting choice of variable names,

if = if (end>else) then
  (end)
else
  (else)
endif

I guess this would also help with syntax parsing.

Beliavsky commented 3 years ago

I support the function syntax. My suggested name for ifthen is lazy_merge, which would emphasize that this function behaves differently from other Fortran functions, where all arguments are evaluated. Lazy_pick or lazy_choose are also possible names.

klausler commented 3 years ago

This is not the same thing as lazy evaluation as the term is commonly understood in programming language theory.

certik commented 3 years ago

@klausler here is a draft of a paper for the intrinsic approach: https://github.com/j3-fortran/fortran_proposals/pull/213, can you please help me finish it, so that I can submit it to the committee as an alternative?

urbanjost commented 3 years ago

It is a lot easier to do in an interpreted language, and some might not like the reuse of IF but in a little scripting language I have where everything is a function IF acts like a function if given more than one parameter and only evaluates one of the following expressions depending on the results of the conditional, in the form if(expression,eval_if_true, eval_if_false). I have used it so long it seems natural to me. Trying to put that into a Fortran context it might look like

 program testit
contains
   call passto(20)
   call passto()

subroutine passto(a)
integer,optional :: a
integer          :: b
   b=if(present(a),a,10)
   write(*,*)b
end subroutine passto

end program testit

I can think of some reasons that might be disliked, but I have seen a lot of comments about the complexity of some of the solutions and I have used that for a long time and it is pretty easy to type even interactively. The language also lets logicals return an integer and a lot of other un-Fortranish things so things like like doing a sum() of a bunch of expressions and being able to do something if 2 out of 3 are true is easy, or doing a max() or min() on a list of logical expressions makes sense; but now that there is ANY() and ALL() Fortran can do something similiar now. Of course only evaluating one of the expressions is easy in a scripting language and very much against standard Fortran behavior.