How do you use Fypp?

Fypp is amazing, and I’d like to use it effectively.
There’s a lack of discussion on how to use it correctly.

Kindly share any tips that might be helpful for everyone.
Mainly, what difficulties you faced, and what helped you.

To start,
Here’s what I found works for me …

  1. Create a separate file for Fypp code, and write the template code in it.
  2. Create a normal Fortran file, and using Interfaces, create a wrapper around the subroutine generated by Fypp. This ensures that the IDE we’re using, has access to a subroutine signature which it can use for auto-completion suggestions, linting, etc.
  3. Creating this wrapper, is sometimes extra work, and tedious, so I just copy the subroutines arguments from the code generated by Fypp. In future, I will automate this process with some shell scripts.
  4. Writing Fypp code is hard, since we don’t have auto-completion, jump-to-definition, and other LSP features. Maybe fortls will one day support it. But until then, I have found it easier to …
    • First develop a function let’s say quicksort normally in Fortran, only for a single type, like integer.
    • This ensures that we can take full advantage of all of the amazing features of our IDEs, auto-completion, debbuggers, etc.
    • After our basic code works, copy it to the fypp file, and template it.
    • During templating your code, first only include the subroutine arguments, and template them. Then, run your build tool to make Fypp generate the template code. You can now copy the code directly, and use it as your wrapper layer I mentioned above. This saves us IMMENSE amount of time.

Kindly share what has helped you.

Thanks.

4 Likes

We are using Fypp in a very similar workflow, as the one you describe (see for example the mpi-broadcast wrapper in the MpiFx library).

  • We define Fypp-macros for each template we wish to use.
  • We then generate the actual routines by invoking the Fypp-macros with the right arguments.

This approach gives well defined, managable code. Also, by separating template and template generation, it would be easier to transform all this stuff into proper Fortran templates, once they are standardized and implemented in various compilers.

We usually put the template definition macros and the template generation calls into the same source file, but I like your separated approach, as it results probably in cleaner code.

As for the generated interface files: we do not use them, but your idea is appealing, especially as it may indeed be a big help for IDEs. In theory, you can generate those interface definitions even with Fypp, if you mark the routine starts and ends appropriately (see the example code below). I am not saying, you shoud do it with Fypp (as it might result in even more clutttered code), but it is at least in theory possible. :wink:

Fypp template expansion with autmatic interface generation
#:mute

#! Defining global variables to register inteface headers and footers
#:set _ifheaders = []
#:set _iffooters = []

#! Adds an inteface header to the list of registered headers
#:def INTERFACE_HEADER(ifheader)
  #:global _ifheaders
  $:_ifheaders.append(ifheader)
  $:ifheader
#:enddef

#! Adds an inteface footer to the list of registered footers
#:def INTERFACE_FOOTER(iffooter)
  #:global _iffooters
  $:_iffooters.append(iffooter)
  $:iffooter
#:enddef

#! Prints registered interfaces  
#:def PRINT_INTERFACES()
  #:for ifheader, iffooter in zip(_ifheaders, _iffooters)
    $:ifheader
    $:iffooter
  #:endfor
#:enddef

#:endmute
  

#! Define the template to be generated with various paramaters
#:def my_template(VARTYPE)

#:block INTERFACE_HEADER

  subroutine my_routine(var1, var2)
    ${VARTYPE}$, intent(in) :: var1
    ${VARTYPE}$, intent(out) :: var2
      
#:endblock

    ! Some Fortran code in the body of the routin
    print *, var1, var2
      
#:block INTERFACE_FOOTER
  end subroutine my_routine
#:endblock
  
#:enddef my_template


#! Generate code from the template
#:for VARTYPE in ['real(wp)', 'integer', 'logical']
  @:my_template(${VARTYPE}$)
#:endfor


interface
    
#! Print the registered interface
@:PRINT_INTERFACES()

end interface
2 Likes

This is very useful! @aerosayan and @aradi, NumPy recently started championing the use of fypp as well, would you be willing to write an extension to the documentation there with these tips?

Yes, the idea is to make it to be friendlier for the IDEs.
Though, it’s too much work to write the interface/wrapper code manually.

I’m thinking of writing the files generated by Fypp, directly in the source directory.

This would allow the IDEs to see the formal Fortran code, and all features like LSP, debugging, etc. can work as intended.

In theory, it would simplify everything.

Debugging would always have to be on the source files.

For LSP I am not sure, since fypp is Python you would need to parse the Python AST and mix it with the Fortran AST. That’s probably complicated.

Might try integrating fypp into fortls, but I’m not making any promises since that’s a lot of work.

@rgoswami I might be able to contribute if it is considered to be useful. Maybe, we can discuss details on the CECAM-ESL meeting?

I’m a big user of fypp, and would love to see at least some support for it in emacs’s f90-mode (yes, some of us are still stuck in the last century…)

Rich

Sure, sounds great, looking forward to it :smiley: