202X feature: Conditional Expressions

The Fortran Standards Committee is considering proposals for “Conditional Expressions”. An example:

Original code:

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

I would like to encourage the wider community to leave feedback on this regrading which is more readable or what the committee should do at https://github.com/j3-fortran/fortran_proposals/issues/183

4 Likes

@kargl, thank you. I personally also don’t like either of the options. But I want to get as much feedback on this from the community as possible, so that I can relay the feedback to the committee. Here is the proposed syntax, I hope I translated the example correctly:

<conditional-expr> ::= (? <scalar-logical-expr> | <then-expr>
                       [ :? <scalar-logical-expr> | <elseif-expr> ]...
                       : <else-expr> ?)

<conditional-arg> ::= (? <scalar-logical-expr> | <consequent-arg>
                      [ :? <scalar-logical-expr> | <consequent-arg> ]...
                      [ : <consequent-arg> ]
                      ?)

Perhaps it is meant to look like this:

CALL SUB(A, B, C, (? (PRESENT(D)) D :? (X < 1) EPSILON(X) : SPACING(X) ?))

@kargl what you write seems to be similar to this comment to try to stick to the C convention: https://github.com/j3-fortran/fortran_proposals/issues/183#issuecomment-703768480

The original form is clearer to me. People without exposure to C and little programming experience can figure out the meaning of the original code without much trouble while the C style expression needs further knowledge, and it just one more way to do the same thing. The C style expression with written keywords is just too verbose and defeats the only thing going for this which is conciseness.

I’m gonna say that although I’m not very fond of python, I do agree with the motto “Special cases aren’t special enough to break the rules” and this seems like an example of it.

6 Likes

Thank you everybody for the feedback. More syntax forms are being considered:


At this point, using the C syntax, or the “arrow syntax” (same as C, but using -> instead of ?) is the most readable (from all the alternatives) that some people are actually in favor of. To be honest, I still find the “original form” the most readable, because nested conditional expressions become unreadable to me quickly.

3 Likes

Just a reminder that the committee will be voting again next Monday about this:

and I still don’t like either the keyword or the ? syntax and I would recommend not to do this feature at all.

3 Likes

An intrinsic function seems like the best solution, but failing that, why not:
cond .then. A .else. B ?

It looks like Fortran because it is already syntactically valid. And you could throw in .andthen. and .orelse. for short-circuiting boolean operators.

4 Likes

Thanks for the suggestion @ashe and welcome to the forum! I have not seen this idea yet.

I agree, but the original code has logic that should be within subroutine sub, not in the caller. A local variable d_ could be set appropriately. Fortran already has the ability to write call sub(a,b,c,d) even if d is not present in the caller.

IMO an unglamorous strength of Fortran is that you don’t need to be as smart to use it effectively or read it as some other programming languages, and the proposals are not in that spirit.

1 Like

I feel like the use of if (present(x)) then x endif is perhaps an over-simplified use case. As you say, the called subroutine can easily handle this case itself. But this stops being true if sub is called in multiple places, with different requirements in each case. e.g. if you had something like

function even_factorial(x) result(y)
  y = factorial(if (modulo(x,2)==0) then x else x+1 endif)
end function

function odd_factorial(x) result(y)
  y = factorial(if (modulo(x,2)==1) then x else x-1 endif)
end function

I use a default function in my codes, similar to optval in stdlib, that accepts two arguments, the second optional, and returns the second argument if present, otherwise the first. So I’d write the code above as

call sub(a,b,c,default(merge(epsilon(x),spacing(x),x<1),d))

Since epsilon(x) and spacing(x) do not require computation, this is fine. When the default values of an expression do require considerable computation, then the wordy if block is required to avoid redundant computation.

In general, Fortran does not have lazy evaluation. Would it be possible to have an intrinsic function, say lazy_merge(x,y,true_or_false), that breaks this pattern and for which the compiler evaluates the third argument first and then evaluates the first or second as appropriate? This would simplify a lot of code. I guess this is just different syntax from what is being proposed?

P.S. Why does the committee use all upper case in code examples?

1 Like

See here: Use lowercase for Fortran code in the standard and in proposals · Issue #56 · j3-fortran/fortran_proposals · GitHub

I’m under the impression syntax along the lines of cond .then. A .else. B was brought up a while ago during verbal discussions at a subcommittee meeting but the response was the language with its base of existing processors supporting both free-form and fixed-form source forms needed a “beginning” and “ending” set of tokens and thus came about the options of inline if .. then .. end if constructs and ? .. ?, ( .. ) forms, etc…

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.

1 Like

This is being discussed as ifthen in the thread cited by the OP.

Compilers already have to handle cond .then. A .else. B if there are user-defined operators named .then. and .else.. They need to be able to parse this in either source form. So I don’t see what the problem would be.

@ashe @FortranFan

IIRC, Fortran does not impose the “left-to-right” evaluation order for an expression (i.e. “terms can be evaluated in any order for maximal efficiency”), so isn’t the user-defined operators in this form not guaranteed to work as expected?

[ Edit ]

I guess attaching parentheses like (cond .then. A) .else. B may be useful, but the end result is probably the same as merge(A, B, cond), in that both A and B are evaluated?

Btw I’ve played with a ternary expression and merge() below, and the latter seems to evaluate only one of A and B for gfortran-10… (but I guess this is not guaranteed to behave the same for different compilers?)

test code
// test.cpp
#include <iostream>
using namespace std;
int f(int n) { cout << "(f called) "; return n * 10; }
int g(int n) { cout << "(g called) "; return n * 100; }

int main() {
    int n;
    cin >> n;
    cout << ( n > 0 ? f(n) : g(n) ) << endl;
}
$ g++-10 test.cpp
$ echo 2 | ./a.out
(f called) 20
$ echo -2 | ./a.out
(g called) -200
!! test.f90
integer function f(n); print *, "(f called) "; f = n * 10  ; end
integer function g(n); print *, "(g called) "; f = n * 100 ; end

program main
    integer n, f, g, ans
    read *, n
    ans = merge( f(n), g(n), n > 0 )
    print *, ans
end
$ gfortran-10 test.f90
$ echo 2 | ./a.out
 (f called) 
          20
$ echo -2 | ./a.out
 (g called) 
        -200

I am not happy with either form, but do like Bob Corbett’s proposed syntax in 21-159.

@wclodius make sure you vote in the poll please.

First, there are sufficient differences in the semantics involved with conditional expressions, particularly with chaining, to make the analogy with user-defined operators inapplicable here. Secondly, the rules with the order of operations are fraught with so many practical difficulties, one has to bring in a beginning and ending token anyway for any deterministic usage, the tokens being parentheses.