Arguments of MERGE()

I have a question about this code:

program mergex
   integer :: i=1
   call sub(i)
   call sub()
contains
   subroutine sub( i )
      integer, intent(in), optional :: i
      print *, merge( i, 0, present(i) )
   end subroutine sub
end program mergex

The question is whether it is allowed to use i as the first argument to MERGE() when it is not present. I cannot tell from reading the f2023 standard section 16.9.139 if this is legal. There is a comment in MFE section 9.15.1 that suggests that it is legal.

The code fails with gfortran and flang, but works “as expected” with nagfor.

[edit] The print statement could be replaced with

   print *, present(i)?i:0

but the three compilers I mentioned above do not yet support that operator.

This is not legal because there is normally no short circuiting, i.e. the compiler may evaluate the first two arguments indepentdenly of the value of the third.
I think this was the motivation for the new F23 ternary operator.

2 Likes

Your first 2 arguments in merge may both be evaluated anyway; the condition/mask won’t necessarily prevent that. In such cases, I simply go back to plain if instead.

EDIT: I just saw it’s mentioned here on fortran-lang.org, too.

1 Like

Stdlib has the OPTVAL procedure specifically for this case. Several syntax solutions have been proposed, but the simplest improvement (adding a second parameter to PRESENT that contains a default value) seems so obvious that it is odd PRESENT does not have that; but even the TEST?TRUEVAL:FALSEVAL syntax does not eliminate having to often create a scratch variable to hold the value determined from the value if present or the default. Being able to use the parameter name and set a default value (perhaps in the declaration of the variable) would have been so much nicer. If you have a procedure with a number of optional parameters you end up with a rather long block of code just setting the values, generally into scratch variables.

All three arguments must be evaluated before the function is performed. Non-present arguments are usually represented by passing a null pointer. So your code will dereference the null pointer and usually abend. (Insert usual warnings about starting WWIII.)

Conditional expressions require parens. So your example would be:

      print *, (present (i) ? i : 0)

Looks like gfortran will have it very soon.

1 Like

The ‘avoid divide-by-zero’ case is also one where merge won’t reliably work.

You mean:

y = merge( 1.0/x, 0.0, x /= 0.0 )

Or this double merge to avoid division by zero:

merge(1.0/merge(x,1.0,x /= 0.0), 0.0, x /= 0.0)

If the latter, why won’t it work reliably? (Not that I’m a fan of this expression, but I am curious).

Your ‘double merge’ should work fine!

Nonetheless, I’m looking forward to F2023 conditional expressions. When appropriate, I like using them in other languages.

I thought this was exactly the kind of expression that MERGE() was designed for.

I think it would be more useful if this were possible. As it is, it still has certain advantages over if and where (most notably its integration into expressions), but the evaluation of both branches limits its use.

Merge is just an elemental function. If you think of it in terms of arrays, you’ll see that it’s only intended to replace the “false” portions of some “array” with certain “fallback” values:

res = merge(array, fallback_values, array >= 1 .and. array <= 5)

So, it’s more a part of the functional filter/map/reduce (e.g., the where and forall statements/constructs, and the pack, unpack and reduce intrinsics), rather than an operator.

4 Likes

Not if you pass the -C=present option to nagfor(1) ; the program, as written, is not conforming, but most compilers do not insert sufficient error-checking in generated code to discover that fact.

Note: -C and -C=all will also enable this runtime check.

1 Like

I guess my original question then was whether it was allowed to pass that null pointer into MERGE if it was then later never referenced? The statement in MFE says that it is allowed for that argument be be undefined, but it was not clear to me if that included the “not present” condition. Then nagfor seemed to disagree with the other compilers (now I see @themos reply), which contributed to my uncertainty.

It looks like I’ll need to fall back and use an if expression for now. Maybe later I can shorten it with the (x?y:z) syntax (with the parentheses :wink: ).

The Fortran standard doesn’t detail a calling sequence. So a null pointer may or may not be indicative of a non-present optional argument.

One case of this, in gfortran, is when the optional argument is also specified to be passed by value. There is no pointer, so no way to use that as an indicator of presence. Gfortran and others generate a ‘hidden’ boolean argument on the calling side that tells whether the argument is present or not. The called procedure can then use this to determine presence.

The hidden argument is similar to how the lengths of character variables are passed. In fact when gfortran first implemented it, they placed the hidden optional/value presence args before the hidden character variable lengths. I mentioned to the gfortran developers that the IBM compilers did it the opposite way (char lens first, presence args second) and they quickly switched gfortran to match what IBM did.

One can use the argument names in an associate statement to avoid using scratch variables.

associate(arg1 => OptVal(arg1,arg1_default), arg2 => OptVal(arg2,arg2_default))
1 Like

Why is a nonpresent dummy argument not allowed in MERGE() but is allowed in OptVal()?

Because the first argument of optval is optional, whereas all the arguments of merge are required.

Since merge accepts any type and rank, the underlying implementation might require a valid pointer- or array-descriptor.

That optval function is limited to primitive types, so it could become obsolete once the F2023’s conditional expression becomes ubiquitous.

1 Like

Appealing when arg1 and arg2 are INTENT(IN) but using the same name causes some confusion when intending to alter the argument value when it is present. Inside the associate block arg1=10 would not change the value of the argument arg1. In longer procedures it would be easy to miss that and think the argument return value is being set.

In the code I posted, there was no local variable, I just printed the value from the expression directly. In my actual code, I am assigning the value to a local variable with a different name. I have considered in the past using this incantation to set a local associate variable, but I have decided against it for this reason. It does simplify things in some ways, but having an associate name be the same name as an actual variable can also be confusing.

Thanks to everyone for the enlightening comments. I will for now proceed in my code with the if() statement (maybe written on a single line just to be concise), and in a few years when it is popular among compilers, I will switch to using the f2023 (x?y:z) syntax.

If you add a stylised comment ! TODO CONDEXPR, you would be able to later quickly find all the places in your code where conditional expressions could be used.

2 Likes