In the short run we should simply make fpm
call fypp
in PATH to pre-process .fypp
files automatically. That might end up being the only thing we will do for .fypp
files.
A separate question is what it would take to rewrite fypp
or something similar in functionality into Fortran, so that we can link it with fpm
, so that fpm
is standalone (does not depend on Python). The main target is stdlib
. Let’s look at the usage in stdlib
. Below I collected the main uses cases. Did I miss any?
We can use these to design a pre-processor that is possible to implement in Fortran. If these are all or most of the use cases, they can be separated into two independent kinds:
- generic over any type (either one or a combination of integer, real, complex, logical) and kind
- generic over any array rank
The first one is I think quite simple to implement (i.e., design a syntax, preferably as close to fypp as possible) and implement in Fortran. The second one is more complicated, as one has to do things like select subarrays and handle declarations properly, but still it seems very much doable.
Both of these main use cases seem to 100% fit as a subset into the generics effort in the Fortran Standards Committee. So in fact we should design this well as a community and use stdlib and other codes as examples of real world usage. Then the generics subgroup at the committee can take this and design some syntax for the language. Our pre-processor would work as a prototype for this. (The generics effort in Fortran is wider, we are also designing how to do “templates/traits”, but that is a separate problem that we do not seem to need here.)
Use Cases
1: Loop over all integer, real, complex and logical kinds
Loop over all integer and real types and kinds:
#:set IR_KINDS_TYPES = INT_KINDS_TYPES + REAL_KINDS_TYPES
...
#:for k1, t1 in IR_KINDS_TYPES
elemental function clip_${k1}$(x, xmin, xmax) result(res)
${t1}$, intent(in) :: x
${t1}$, intent(in) :: xmin
${t1}$, intent(in) :: xmax
${t1}$ :: res
res = max(min(x, xmax), xmin)
end function clip_${k1}$
#:endfor
To loop over complex and logical kinds also: stdlib/stdlib_optval.fypp at 492543d0086ff3821ecf2ee8f0a9cf46a945a7ac · fortran-lang/stdlib · GitHub
#:set KINDS_TYPES = REAL_KINDS_TYPES + INT_KINDS_TYPES + CMPLX_KINDS_TYPES + &
& [('l1','logical')]
This is by far the most common use case. This one would be easy (I think) to implement in Fortran.
2: Loop over all array ranks
#:for k1, t1 in INT_KINDS_TYPES
#:for rank in REDRANKS
#:set RName = rname("moment_mask_scalar",rank, t1, k1, 'dp')
module function ${RName}$(x, order, dim, center, mask) result(res)
${t1}$, intent(in) :: x${ranksuffix(rank)}$
integer, intent(in) :: order
integer, intent(in) :: dim
real(dp), intent(in) :: center
logical, intent(in) :: mask${ranksuffix(rank)}$
real(dp) :: res${reduced_shape('x', rank, 'dim')}$
if (dim >= 1 .and. dim <= ${rank}$) then
res = sum(( real(x, dp) - center)**order, dim, mask) / count(mask, dim)
else
call error_stop("ERROR (moment): wrong dimension")
end if
end function ${RName}$
#:endfor
#:endfor
This use case also seems possible to do in Fortran.
3. Different code path for complex type
#:for k1, t1 in RC_KINDS_TYPES
#:set RName = rname("cov",2, t1, k1)
module function ${RName}$(x, dim, mask, corrected) result(res)
...
${t1}$ :: res(merge(size(x, 1), size(x, 2), mask = 1<dim)&
, merge(size(x, 1), size(x, 2), mask = 1<dim))
...
#:if t1[0] == 'r'
res = matmul( transpose(center), center)
#:else
res = matmul( transpose(conjg(center)), center)
#:endif
...
end function
This could be done by introducing a function in the pre-processor such as:
#:if is_real(t1)
res = matmul( transpose(center), center)
#:else
res = matmul( transpose(conjg(center)), center)
#:endif
4. rname
#:set RName = rname("cov",2, t1, k1)
module function ${RName}$(x, dim, mask, corrected) result(res)
I actually don’t know what this does. I assume it creates the name of the function somehow. This seems minor and possible to do in Fortran also.
5. Loop over each dimension up to a rank, and select_subarray
#:for fi in range(1, rank+1)
case(${fi}$)
if (present(center)) then
do i = 1, size(x, ${fi}$)
res = res + (x${select_subarray(rank, [(fi, 'i')])}$ - center)**order
end do
else
allocate(mean_, source = mean(x, ${fi}$))
do i = 1, size(x, ${fi}$)
res = res + (x${select_subarray(rank, [(fi, 'i')])}$ - mean_)**order
end do
deallocate(mean_)
end if
#:endfor