stdlib icon indicating copy to clipboard operation
stdlib copied to clipboard

optval and module dependencies

Open ivan-pi opened this issue 2 years ago • 8 comments

So there have been a few discussions about optval before:

  • https://github.com/fortran-lang/stdlib/issues/524
  • https://github.com/fortran-lang/stdlib/issues/448

The former thread established the performance implications of sprinkling optval here and there are likely negligible. The latter thread established that merge cannot be used as a replacement due to it's behavior interacting with the present function, and also being unsuitable for variables of type character(:), allocatable. However, I think one comment from @zoziha is worthy of more attention:

Because merge is a standard function, we can reduce optval module dependencies by using it(merge). It is convenient to replace them with the conditional expression syntax (F202X) in the future.

In my opinion the stdlib modules should be as independent as reasonably possible. This enables users to simply copy the sources of the modules they want to use, without having to climb up the module dependency tree. Now I realize this is a non-issue for those using stdlib via fpm, but some users might prefer to preserve their current build methods.

For source files that require the preprocessor anyways, it would be straightforward to provide a preprocessor version of optval instead:

#:def optval(lhs,opt,default)
  ${lhs}$ = ${default}$
  if (present(${opt}$)) ${lhs}$ = ${opt}$
#:enddef

An alternative variation is

#:def optval(lhs,opt,default)
  if (present(${opt}$)) then
    ${lhs}$ = ${opt}$
  else
    ${lhs}$ = ${default}$
  end if
#:enddef

however this one can result in some false positive warning about non-initialized arguments.

[Edit: hopefully the macros are correct now...]

ivan-pi avatar Nov 24 '21 20:11 ivan-pi

I like the idea of using the pre-processor for internal use of optval

#:def optval(lhs,opt,default)
  lhs = default
  if (present(opt)) lhs = opt
#enddef

Would this not require the variables to be called exactly lhs, opt, and default? Or does fypp know that this is a function call and to substitute variables?

Now I realize this is a non-issue for those using stdlib via fpm,

Eventually fpm should tree-shake, i.e. build only the modules used by the project and not the whole stdlib, so this may in the future have an effect on fpm build as well.

milancurcic avatar Nov 24 '21 20:11 milancurcic

good point @ivan-pi I use a similar approach in my own code with fypp:

#:def myfunction(var2,var4,var3)
(var1(${var2}$)#{if var3 != '0' }#+${var3}$#{endif}#)*var5#{if var4 != '0' }#+${var4}$#{endif}#
#:enddef

where var2..4 can be names of variables or integer (e.g.,0) It is called in the code as:

result = $(myfunction( myvar1, myvar2, 0)}$

this would be available for all files if stored in common.fypp of stdlib

jvdp1 avatar Nov 24 '21 20:11 jvdp1

Forgive me, I only glanced quickly through the fypp docs. I think the following macro is correct:

#:def optval(lhs,opt,default)
  ${lhs}$ = ${default}$
  if (present(${opt}$)) ${lhs}$ = ${opt}$
#:enddef

and can be called as follows:

subroutine demo(left)
  integer, intent(in), optional :: left
  integer :: l 

  @:optval(l, left, 1)

end subroutine

producing the output

$ fypp test_optval.fypp 


subroutine demo(left)
  integer, intent(in), optional :: left
  integer :: l 

  l = 1
  if (present(left)) l = left

end subroutine

The downside is the assignment is hidden in the macro which makes it a bit cryptic.

ivan-pi avatar Nov 24 '21 21:11 ivan-pi

I see. And it can't be used in expressions, correct?

milancurcic avatar Nov 24 '21 21:11 milancurcic

I see. And it can't be used in expressions, correct?

Not in the form proposed by @ivan-pi because the macro will be replaced by the two lines before compilation. However, I cannot think about a way to do that, without conditional expressions or intrinsics.

jvdp1 avatar Nov 24 '21 21:11 jvdp1

Correct, an expression implies having a function or using the F202X syntax:

l = present(opt) ? opt : default

This can in principle be wrapped in a macro:

#:def optval(opt,default)
present(${opt}$)) ? ${opt}$ : ${default}$
#:enddef

which can be used as an expression using a similar syntax to the one Jeremie showed:

l = @{optval(left, 1)}@

but IMO it doesn't feel worth the effort.

I'm not sure if fypp macros can be overloaded, however you could pull both versions off in the same macro by using either the positional or keyword arguments that fypp supports.

ivan-pi avatar Nov 24 '21 21:11 ivan-pi

On the other side, are we not proposing a shortcut for a function (optval) we find useful but that we don't want to use because the compilers is not able to inline it properly by default? This might give a bad image of optval to the stdlib users (and to some extends, also to the committee)

jvdp1 avatar Nov 24 '21 21:11 jvdp1

Personally I'm not worried too much about the performance penalty. As long as not used in a performance-critical context I think it's handy. On the other hand with the new F202X conditional syntax I think optval becomes superfluous.

The reason I wanted to suggest a preprocessor alternative was merely as a means of reducing inter-dependencies between modules, allowing users to "pick-n-play". You could argue that downloading three modules (kinds, optval, selection) instead of two (kinds, selection) is not a big difference.

ivan-pi avatar Nov 24 '21 22:11 ivan-pi