To: J3 J3/23-202 From: Aleksandar Donev & Generics Subject: Packaging long argument lists of templates Date: 2023-June-14 1. Background ============= This paper relates to one of the unresolved technical issues (UTIs) in the current generic programming proposal. We are all familiar with Fortran 77 procedures with very long lists of arguments, and how error prone and tedious in typing it is to call them correctly with the right number of arguments and in the right order. Cut and paste are commonly used, but errors are very hard to catch since a code may compile even if there are swapped arguments, for example, and the error can then be cut and paste many times. Fortran 90 solves this problem by allowing one to package multiple parameters into a derived type, which acts as a container that can store multiple related variables/parameters. Only a single scalar argument of that derived type needs to be passed by the user. Default values for many parameters can be specified as initializations of variables inside the derived type definition. This further avoids using long lists of default arguments. 2. The Problem ============== The same situation that happens for procedure argument lists happens for template arguments in INSTANTIATE statements in the current proposal for generic programming. There is no mechanism to package a set of types and a set of operations acting on those types into a unit that can then be passed as an argument when instantiating templates. Such a unit has been referred to by others as a signature (taken from Ada's rationale/textbook) or witness. We believe the lack of ability to encapsulate long argument lists in the current design is a shortcoming, but one that is difficult to resolve. This paper proposes to learn from Ada 2005/2012 in exploring solutions. 3. Rejected Solution: TBPs ========================== It is worth noting that the object-oriented features of F2003 allow a type to be used as a mechanism to package a type along with operations as type-bound procedures. This mechanism is not available in templates in the current proposal since there is no way to even have CLASS(T) in a template, where T is a deferred type. However, there are some deficiencies with using TBPs to package procedures acting on objects of a given set of types. Most importantly, every use of a given template that relies on TBPs will require extending a specific type, which will require writing wrapper types, but can be hard or impossible to combine with existing type hierarchies since we do not have multiple inheritance in Fortran. The solution proposed below can in some sense be seen as an alternative to multiple inheritance, and is similar to the way interfaces are used in Java or other languages as an alternative to C++'s multiple inheritance. 4. Signatures in Ada 2005 ========================= The Ada community was aware of this issue when they added generic programming features to Ada 2005, and has already developed a solution. It is therefore worthwhile to first briefly discuss signatures in Ada as per the Ada rationale. In Ada, generic programming is done via generic packages, which can be thought of as generic (parameterized) modules. In Ada, requirements, templates, and signatures are all just (generic) packages, instead of being three different features. This is flexible but can make the code hard to read and understand. 5. Proposal =========== We propose that subgroup explores solutions to managing long lists of template and requirement arguments. One of the examples in the Ada rationale is taken from scientific computing and is relevant for Fortran. The basic idea in Ada is that packages can be parameters of packages. This way, one can pass to a generic complex vector package a package that implements a generic complex type and a package that implements generic vectors of type R, to produce an implementation of a generic complex vector. In the proposal below, this means that TEMPLATEs can have SATISFACTIONs as arguments. Key to safety is to have a mechanism that ensures that the signatures for the complex type and the vector are based on the same type, R (typically real). A possible approach for Fortran would be to introduce a new concept that is inspired by signatures/witnesses from other languages. A name for the new feature could be SATISFACTION, as in a satisfaction of a named requirement. Instead of trying to write a detailed specification or syntax, an illustration inspired by the example in the Ada rationale is provided. ! ============================================== module basic_requirements requirement ComplexNumber(R,C,cabs) type, deferred :: R ! Type typically a real number type, deferred :: C ! Type typically a complex number elemental function cabs(number) type(C), intent(in) :: number type(R) :: cabs end function end requirement requirement RealArrayOps(R,norm) type, deferred :: R ! Type typically a real number function norm(vec) ! Norm of a vector type(R), dimension(..), intent(in) :: vec type(R) :: norm end function end requirement end module basic_requirements ! ============================================== We want to write a template that provides a vector of complex numbers by combining a package defining a type corresponding to a "complex" number with a package defining a vector of numbers: ! ============================================== module complex_vectors use basic_requirements template ComplexVector(Complex,ArrayOps) ! Complex satisfies the ComplexNumber REQUIREMENT SATISFACTION :: Complex = ComplexNumber ! This brings the names from ComplexNumber into scope import Complex, only: R, C, cabs SATISFACTION :: ArrayOps = RealArrayOps ! Need to specify that the types, R, from both satisfactions ! must be the same type requires same_type(ArrayOps%R, R) ! not currently supported contains function norm(vec) result(norm) ! Norm of a complex vector type(C), dimension(..), intent(in) :: vec type(R) :: norm norm=ArrayOps%norm(cabs(vec)) end function end template ComplexVector end module complex_vectors ! ============================================== Before we instantiate this template we must provide some implementations of the types R and C, and the procedures cabs and norm. Here we use default reals for R, and make C use a polar representation: ! ============================================== module basic_implementations template PolarComplex(wp) integer, constant :: wp ! Working precision type :: PolarComplex real(wp) :: r=0.0_wp, theta=0.0_wp end type contains elemental function cabs(number) type(PolarComplex), intent(in) :: number real(wp) :: cabs cabs=number%r end function end template template IntrinsicRealArrayOps(wp) integer, constant :: wp ! Working precision contains function norm(vec) real(wp), dimension(..), intent(in) :: vec real(wp) :: norm add = sqrt(sum([vec]**2)) ! L2 norm via intrinsic sum end function end template end module basic_implementations ! ============================================== Finally, let us use the template ComplexVector in practice: ! ============================================== program polar_vector use basic_requirements use basic_implementations use complex_vectors integer, parameter :: sp=kind(0.0), dp=kind(0.0d0) integer, parameter :: wp=dp ! Choose what precision we want to use instantiate PolarComplex(wp) instantiate IntrinsicRealArrayOps(wp) ! create packaging of types and operations: SATISFACTION :: Complex=ComplexNumber(real(wp), PolarComplex, cabs) SATISFACTION :: Vector=RealArrayOps(real(wp), norm) ! combine two packages together into a new one instantiate ComplexVector(Complex, Vector), only: cvnorm => norm type(PolarComplex) :: array(10) write(*,*) cvnorm(array) end program polar_vector ! ==============================================