-
Notifications
You must be signed in to change notification settings - Fork 173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to implement same procedures for different numeric kinds #35
Comments
Actually I propose 3c). c) The git repository contains the templated code, depends on a 3rd party tool, and does not contain any autogenerated files. Then we created release tarballs automatically on a CI. A release tarball contains all the necessary generated files and the only dependencies are cmake (or make) and a Fortran compiler, and does not contain the git history, the templated files, nor any CI files and other things that are not needed to actually build the library. Users, as well as distributions (Debian, Ubuntu, Homebrew, Conda, Spack, etc.) only use the tarball, not the git repository. I follow exactly this approach with LFortran, and it seems to work great. The advantage is that the git repository does not have autogenerated files, which greatly simplifies PRs (a simple diff versus hundreds of lines of modified autogenerated files) and makes it obvious how things should be modified --- so that people who want to contribute do not accidentally modify the autogenerated files, or forget to generate the files. Rather, the files are automatically generated using a CI, so they are always generated correctly. |
Great! I didn't think of this and indeed it seems to me like the best way to go. |
The best templating tool I have found is Jin2For. It uses Jinja2 templating which people may be familiar with from web technology stuff and is the templating back end for FORD. These are Python based, which is likely a language that Fortran developers may be familiar with. It can auto-generate default type aliases, kind info, and declarations by querying the compiler and enumerating By providing a generic implementation for each intrinsic kind (where it is sensible to do this) the user doesn't need to care about setting dp, sp, rk, or whatever other convention you have for selecting kinds. Things just work™️. The downside is that compilers are not required to support all kinds, so you end up generating code specific to the kinds that a given compiler supports. This is not necessarily a bad thing, but it means that you may want to distribute different source versions tailored to different compilers. Since Fortran doesn't have a standard/interoperable ABI this is not really an issue at all, IMO. |
I reviewed jin2for and I like its simplicity (a minimal and to the point tool) and the fact that it uses an existing templating language rather than inventing a new one. I think it's a good candidate. |
Let's try to use In general, I think the Fortran language itself should make it easier to write subroutines that operate on different kinds. This is something I would love to experiment with in LFortran in the future, and using |
I gave a try to I am not sure how it should be done with cases that involve both integer and real kinds. Should the pre-defined templates be used? If yes, how to use kinds defined as Anyway, |
What are the disadvantages of using CPP for this? I am worried about deepening the necessity on external tools, which can hinder portability. CPP is We've used it in FMS for this task without much issue. Readability and debugging are the only major drawbacks, but this would be true fur any templating approach. (I'm on the road right now but can supplement with links when I get a chance.) |
As far as I understand CPP is more limited in what can be done with it. I'm surprised that you could do this with CPP alone. In the scenario 3c, the external tool is required only of stdlib developers and not of end users, so I don't see much of a portability issue. |
This post outlines what we can do with FMS with CPP templating: |
Thanks @marshallward, I just read the comment and the sources you linked and I agree, it does seem quite bloated and is likely to get more complicated when considering different combinations of argument types and kinds. I think this illustrates well the downsides -- with CPP we can't loop, but only define/undefine macros and branch. There may be more esoteric stuff to it, but this is what I've seen. @jvdp1 I looked at your templates and the code is quite clean and readable to my eyes. I like it. We probably shouldn't use the .F90 suffix here -- .F90 is still a valid Fortran source file, whereas these templates aren't. I think jin2for suggests .t90 suffix for templates. |
I tried to implement the IO module with CPP template (see https://github.com/jvdp1/stdlib/tree/loadtxt_cpp/src ).
@milancurcic I followed the syntax implemented by @zbeekman in one of his libraries. I agree that the |
@jvdp1 thanks a lot for implementing both approaches. Here they are, side by side:
It seems the |
@certik |
@jvdp1 we have to update our CI to support |
I can see the advantages of I had resisted Jinja2 integration in another project, because I was concerned that the Jinja2 tokens may clash with the native file's own tokenisation (usually various config files); Jinja2's syntax was designed to safely work with HTML and not much else. But I also wondered if I was being too conservative. Are there any known limitations to using Jinja2 on Fortran markup, such as token mix ups? |
Does Fortran use |
Down the road, I would like to prototype some of this limited templated functionality into LFortran and then propose it for the Fortran language itself. |
I was really hoping that lexical macro processing would get re-introduced into this upcoming Fortran standard. In fact, a fleshed-out macro processing specification was already given in Fortran 2008 drafts (e.g., https://j3-fortran.org/doc/year/07/07-007.pdf) but was dropped then. It was also considered for the upcoming standard as a means of supporting generic programming. But we know how that went. After templating/macro processing was forgone for F2020, I looked into |
I slightly modified the |
One thing I don't understand about the
The main downside of this approach is the repetition of each subroutine "skeleton" and the need to manually populate the interface blocks, but the |
With this proposed scenario, 1 file per skeleton would be needed (if I understand well your proposition), while with the The However, if we use a strategy as described by @certik where end users and distributions only use tarballs automatically generated by a CI, using an external tool should not be a problem. |
I didn't show it, but you would
|
I haven't gone through this thread in the detail it deserves yet, but a few broad observations:
The biggest problem with the automatically generated types is that they are very non-portable: They basically interrogate the available kinds from My personal preference is to use Jin2For, but don't use the built in type declarations, aliases and kinds that are created from compiler introspection. Instead, for reals at least, attempt to target single, double and quad precision. CMake introspection can be used to confirm which kinds exist for a given compiler and then Jin2For can be used to generate interfaces and implementations for each kind supported by the compiler. Otherwise, if you use the built in Since the existence of various kinds is not guaranteed by the standard, much less the integer associated with each kind, this is a rather sticky situation. But I would rather loop over a list of kinds (possibly generated from CMake introspection) using Jin2For templates than contend with the awkward square peg in a round hole that is CPP and other non-standardized pre-processing. The advantages of Jin2For (or Jinja2 really...) are its widespread use in other domains (so that it is battle hardened and good enough to be popular) and the fact that it's Python based and extensible. But, by preprocessing the code for the end user so they don't need Jin2For (unless we want to provide different pre-processed code for different compilers) you lose a non-trivial quantity of its utility. Whereas if you can stick to standardized fortran and a subset of cpp/fpp that's implemented in all major compilers then the user can do the code pre-processing themselves at configure/build time. |
The ideal solution for this would be j3-fortran/fortran_proposals#128 in my opinion. But we'll have to probably wait some time for that. |
As an alternative to Jin2For, you may also consider the Fypp preprocessor for generating templates. (Disclaimer: I am the main author of Fypp). It has similar loops as Jin2For and additionally also offers macros, so it could be also used for the assert macros (#72). It consists of a single (Python) source file and can be, therefore, easily shipped with the library, so that the build only requires a standard Python (2.6, 2.7 or 3.x) installation. |
I like fypp a lot. Having the author in Fortran community is a huge plus IMO. What do you think about taking a minimal example function and comparing the jin2for and fypp syntax next to each other? For example: integer function sum(a, b)
integer, intent(in) :: a
integer, intent(in) :: b
sum = a + b
end function sum Requirements:
The preprocessed source code would result in 49 specific functions. What would the template look like with jin2for and fypp? What would the invocation look like? Let's compare them side by side. |
Here's a go at using The only snag I ran into was
Not sure if that's a bug or if |
@nshaffer fypp currently does not support multi-level tuple unpacking due to technical reasons. It could be extended if this really made a huge difference in the user experience. (As it is a preprocessor, I try to keep it as simple as possible to prevent people doing something with it, which they should do in Fortran instead 😉) You example is very neat. In some cases we may also need to loop over ranks. It would then need a simple macro and an additional loop more:
|
@aradi Cool, thanks for confirming that about tuple unpacking. I think it's not a problem overall, I just let my Python instincts take the reins. The less-fancy version I arrived at is arguably better. |
@aradi @nshaffer Thank you for the examples with
An additional advantage is that the author @aradi is involved in the Fortran community. |
fypp is interesting. Knew it existed but had not tried it. I have used m4 for that type of thing and just never liked the syntax. So I have used the bash shell and "here" documents. I made make(1) rules for the suffix ".shf" that say to execute the file as a bash shell and use the standard output to make a *.f90 file. Works great for me, and bash has all the looping and access to output from commands and can query the system type and everything itself
Since bash is readily available and I am quite familiar with it that has worked great for me, although I
doubt I will get any converts by mentioning it. Anyway maybe I will quit doing that after giving fypp a better look. Thanks for the enticing examples
… On January 13, 2020 at 1:54 PM Jeremie Vandenplas ***@***.***> wrote:
@aradi https://github.com/aradi @nshaffer https://github.com/nshaffer Thank you for the examples with fypp.
fypp seems quite flexible, and I like @aradi https://github.com/aradi 's example with loops over ranks (we may actually need that for functions like mean(array), variance(array), ... :) ). So I think this feature has to be considered seriously. Such a feature may be tedious to implement with cpp (and I don't know if it would be possible with jin2for).
An additional advantage is that the author @aradi https://github.com/aradi is involved in the Fortran community.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub #35?email_source=notifications&email_token=AHDWN3OBAQFOPEHPDRCGEF3Q5S2GJA5CNFSM4J6MKIL2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEIZ3S6A#issuecomment-573815160 , or unsubscribe https://github.com/notifications/unsubscribe-auth/AHDWN3ODMUOOVUG37ODXOZDQ5S2GJANCNFSM4J6MKILQ .
|
Here Earlier I also did it with Without any research (I could have spent more time in |
We've been using fypp for this and it's been working well enough. |
This question comes up in #34 and elsewhere. How to implement specific procedures that work on different kinds (
sp
,dp
,qp
,int8
,int16
,int32
,int64
) as well as characters, where the body of the procedure is the same (can be copy/pasted entirely without breaking it). Let's first just focus on this scenario, and we can consider more complex cases later.I know of a few approaches:
Repeat the code, that is, implement all specific procedures explicitly. That's what I did in functional-fortran, see https://github.com/wavebitscientific/functional-fortran/blob/master/src/lib/mod_functional.f90. Repeating is fine if you do it once and forget about it. The upside is that you can see the specific code and it needs no extra tooling. The downside is combinatorial explosion if you have procedures that are to handle all combinations of types and kinds. Most procedures are rather simple (one or two arguments), and I ended up with > 3K lines of code for 23 generic procedures. Most work was in editing the argument types to specific procedures, and less work was in copy/pasting of the repeatable content. I don't recommend this approach for stdlib.
Approach 1 can be somewhat eased by explicitly typing out the interfaces, and using
#include 'procedure_body.inc'
, defined in a separate file. Then your procedure body collapses to one line. This reduces the total amount of code, but not so much the amount of work needed, as most work is in spelling out the interfaces. This approach still doesn't need extra tooling as a C preprocessor comes with all compilers that I'm aware of.Use a custom preprocessor or templating tool. For example, a function that returns a set of an array:
A template could look like this:
or similar, where the custom preprocessor would spit out specific procedures for all integer and real kinds. Some additional or alternative syntax would be needed if you wanted all combinations of type kinds between arguments.
There may be tools that do this already, and I think @zbeekman mentioned one that he uses. In general, for stdlib I think this is the way to go because we are likely to see many procedures that support multiple arguments with inter-compatible type kinds. The downside (strong downside IMO) is that we're likely to introduce a tool dependency that also depends on another language. If the community agrees, we can use this thread to review existing tools and which would be most fitting for stdlib.
Let's say we pick a tool to do the templating for us, we have two choices:
a) Have user build specifics from templates. In this scenario, the user must install the templating tool in order to build stdlib. I think we should avoid this.
b) Use the templating tool as developers only, and maintain the pre-built specifics in the repo. This means that when we're adding new code that will work on many type kinds, we use the tool on our end to generate the source, and commit that source to the repo (alongside the templates in a separate, "for developers" directory).
Assuming we can find a fitting tool, I'm in favor of the 3b approach here. There may be other approaches I'm not aware of or forgot about. What do you think and any other ideas?
The text was updated successfully, but these errors were encountered: