To: J3 J3/25-150 From: Hidetoshi Iwashita Subject: Additional use cases and suggestion for generic subprograms Date: 2025-June-20 References: 25-102, 25-120r1, 24-143 1. Introduction =============== In paper 25-102, we presented use cases for generic subprograms that demonstrate how accepting coarrays as generic arguments increases the usefulness of generic subprograms. This paper presents additional use cases, focusing on the dependency of local variables on generic dummy arguments. 2. Use case: generic operations/functions with BIGINT type ========================================================== This section introduces examples of extending operations and functions for intrinsic arithmetic types to user-defined derived types. Arbitrary-precision number (bigint) is an integer type that can handle variable-length digits. Intrinsic integer types have digit overflow issues, and intrinsic real types have rounding errors and irreversibility issues. However, the bigint type and the rational type, which represents the numerator and denominator as bigint type numbers, are said to be useful in fields that require reproducibility and accuracy if they can be used in the same way as built-in types for arithmetic operations and built-in/user-defined function calls. In Python, Haskel, and Julia, the bigint type is provided as a built-in type. The program examples introduced in this section have been prototyped and verified using gfortran or flang. However, before compilation, generic subprograms have been manually expanded into equivalent specific subprograms, and @ notation has been manually decomposed into equivalent array expressions or nested loops. 2.1 Framework for BIGINT type The definition of the bigint type is shown below. TYPE :: bigint INTEGER :: sign INTEGER :: size INTEGER(int32), ALLOCATABLE :: data(:) END TYPE bigint The value of the bigint object is as follows: sign * (data(1) + r * data(2) + ... + r **(size-1) * data(size)) where r = 2**31, 0 <= x%data(i) < r We have created minimum necessary parts of the followings for demonstration purposes. - defined assignments = that include type conversion from/to intrinsic arithmetic types, - binary operations +, - and * that include mixed operations with intrinsic arithmetic types, - unary operation - and function ABS, and - defined I/O to read/write decimal character strings Here is one example. The assignment statement from integer x to bigint type y: y = x is defined with a generic interface block: INTERFACE assignment(=) PROCEDURE :: i32_to_bi, i64_to_bi END INTERFACE assignment(=) and module subroutines i32_to_bi and i64_to_bi. And the following subroutine is selected and used if x is of type INTEGER(int32): IMPURE ELEMENTAL SUBROUTINE i32_to_bi(y, x) INTEGER(int32), INTENT(IN) :: x TYPE(bigint), INTENT(INOUT) :: y IF (allocated(y%data)) DEALLOCATE(y%data) ALLOCATE (y%data(1)) y%size = 1 ! because abs(x) is always less than 2**31 y%sign = sign(1_int32, x) ! y%sign will be 1 if x>=0, otherwise -1 y%data(1) = abs(x) END SUBROUTINE i32_to_bi In the prototype of this framework, generic subprograms were not used. Regarding type and kind, since abstraction is difficult when performance is important, it is better to write them specific as shown above. However, in order to support all integer types supported by the processor, it is necessary to use generic subroutines that abstract kind values. Regarding rank, since all assignments and operations except I/O could be implemented as elemental, there was no need to use generic-rank. However, since the ALLOCATE statement is used, the "IMPURE ELEMENTAL" prefix was used in many functions and subroutines. 2.2 SPREAD(SOURCE, DIM, NCOPIES) for BIGINT type As an application of the bigint framework, we extended the intrinsic functions that support integer types to the bigint type. Among the arithmetic type intrinsic procedures in Fortran, support for elemental procedures seems to be possible without using generic-rank by using the IMPURE ELEMENTAL prefix. However, rank-generic must be used for transformational procedures. Here, we define a bigint type function with the same functionality as SPREAD (16.9.197). We have divided it into case (i) scalar version and case (ii) array version. Case (i): GENERIC FUNCTION spread(source, dim, ncopies) RESULT(r) TYPE(bigint), INTENT(in) :: source ! (1) INTEGER, INTENT(in) :: dim INTEGER, INTENT(in) :: ncopies TYPE(bigint), ALLOCATABLE :: r(:) ! (2) IF (dim /= 1) THEN ERROR STOP 'The value of DIM must be 1 if SOURCE is scalar' END IF ALLOCATE (r(max(0, ncopies))) ! (3) r = source ! (4) END FUNCTION spread The scalar value source is the main argument (1), and a one-dimensional array r of type bigint with size max(0, NCOPIES) is returned (2, 3). In the array assignment statement (4), the procedure for assigning from bigint to bigint is called, but since assignment (=) is provided as an elemental subroutine, the assignment is from a scalar to an array. Case (ii): GENERIC FUNCTION spread(source, dim, ncopies) RESULT(r) TYPE(bigint), RANK(1:15), INTENT(in) :: source ! (1) INTEGER, INTENT(in) :: dim INTEGER, INTENT(in) :: ncopies TYPE(bigint), RANK(rank(source)+1), ALLOCATABLE :: r ! (2) INTEGER ext1(dim-1), ext2(dim:rank(source)) ! (3) INTEGER i, nc IF (dim < 1 .or. rank(source)+1 < dim) THEN ERROR STOP 'The value of DIM must be >= 1 and <= RANK(SOURCE)+1' END IF nc = max(0, ncopies) ext1 = [(size(source, i), i = 1, dim-1)] ! (4) ext2 = [(size(source, i), i = dim, rank(source))] ! (5) ALLOCATE(r(@ext1, nc, @ext2)) ! (6) DO i = 1, nc r(@1:ext1, i, @1:ext2) = source(@1:ext1, @1:ext2) ! (7) END DO END FUNCTION spread The type declaration statement (1) is generic, unlike the type declaration statement (1) in case (i), and expresses that source has a rank from 1 to 15.Since the rank of the result r is one greater than the rank of source, it is expressed as RANK(rank(source)+1). References to the rank of generic dummy arguments other than RANKOF are not allowed in 25-120r1, but it is necessary here. Vectors ext1 and ext2 are declared in (3) with sizes that are smaller than the number of dimensions of array r and larger than the number of dimensions of array r, respectively. Here, the expression rank(source) appears in the array-spec of ext2. In array assignment statements (4) and (5), the size of each dimension is obtained by size(source, i). For the allocation of r, the allocate-shape-spec (R935) in the ALLOCATE statement (6) also requires an extension of the @-notation similar to multiple-subscript (R920) or multiple-subscript-triplet (R923). The array assignment statement (7) uses multiple-subscript-triplet (R923). 2.3 SUM(ARRAY [, MASK]) for BIGINT type Consider support for bigint for SUM(16.9.201) without the argument DIM. GENERIC FUNCTION sum(array, mask) RESULT(r) TYPE(bigint), RANK(1:), INTENT(in) :: array ! (1) LOGICAL, DIMENSION(..), INTENT(in), OPTIONAL :: mask ! (2) TYPE(bigint) :: r INTEGER :: idx(rank(array)) ! (3) IF (.not.present(mask)) THEN r = 0 DO @idx = 1, ubound(array) ! (4) r = r + array(@idx) END DO RETURN END IF SELECT RANK(mask) RANK (0) r = 0 IF (mask) THEN DO @idx = 1, ubound(array) ! (5) r = r + array(@idx) END DO END IF RANK (rank(array)) ! (6) r = 0 DO @idx = 1, ubound(array) ! (7) IF (mask(@idx)) r = r + array(@idx) END DO RANK DEFAULT PRINT *, 'Argument MASK is not conformable with ARRAY' END SELECT END FUNCTION sum Generic type declaration statement (1) RANK(1:) follows 25-120r1. It can also be written as RANK(1:MAXRANK()). The handling of the argument mask in (2) is troublesome. Since it is optional, it cannot be a generic dummy argument, but its rank can be 0 or the same as the array, so the decision is carried over to execution time and it is treated as an assumed-rank array. In (3), the size of the loop index variable idx is the rank of the argument ARRAY. As such, the appearance of rank(array) in array-spec is necessary and should be allowed.(4), (5), and (7) use the rank-independent loop proposed in 24-143. This feature is highly desired. (6) is an example where rank(array) appears as a scalar-int-constant-expr in the execution statement. 2.4 SUM(ARRAY, DIM [, MASK]) for BIGINT type Consider support for bigint for SUM(16.9.201) with the argument DIM. Divide the function into two based on whether the result r is a scalar or an array. The result r is a scalar when the rank of the argument array is 1, but that function is easy, so it is omitted here. The function for when the result r is an array is as follows. GENERIC FUNCTION sum(array, dim, mask) RESULT(r) TYPE(bigint), RANK(2:), INTENT(in) :: array ! (1) INTEGER, INTENT(in) :: dim LOGICAL, DIMENSION(..), INTENT(in), OPTIONAL :: mask TYPE(bigint), RANK(rank(array)-1), ALLOCATABLE :: r ! (2) INTEGER :: ext1(1:dim-1), ext2(dim+1:rank(array)) ! (3) INTEGER :: i ext1 = [(size(array, i), i = 1, dim-1)] ext2 = [(size(array, i), i = dim+1, rank(array))] ! (4) ALLOCATE(r(@ext1, @ext2)) IF (.not.present(mask)) THEN r = 0 DO i = 1, size(array, dim) r = r + array(@ext1, i, @ext2) END DO RETURN END IF SELECT RANK(mask) RANK (0) r = 0 IF (mask) THEN DO i = 1, size(array, dim) r = r + array(@1:ext1, i, @1:ext2) END DO END IF RANK (rank(array)) ! (5) r = 0 DO i = 1, size(array, dim) WHERE (mask(@1:ext1, i, @1:ext2)) & r = r + array(@1:ext1, i, @1:ext2) END DO RANK DEFAULT ERROR STOP 'Argument MASK is not conformable with ARRAY' END SELECT END FUNCTION sum The generic type declaration statement (1) declares that the rank of the array is 2 or more. Since the rank of r is one less than the rank of ARRAY, it is expressed as rank(array)-1 in the RANK clause in (2). As in case (ii) in 2.2 and 2.3, this function also makes frequent use of rank references using the RANK function from generic dummy arguments. In (3), (4), and (5), it is referenced in the form rank(array). 2.5 REDUCE(ARRAY, OPERATION [, IDENTITY]) for multiple type Consider support for multiple types including the bigint type for REDUCE (16.9.173) with arguments ARRAY, OPERATION and IDENTITY. Unlike the intrinsic REDUCE, this REDUCE omits the arguments DIM, MASK and ORDERED. GENERIC FUNCTION reduce(array, operation, identity) RESULT(r) TYPE(bigint,fraction), RANK(1:), INTENT(in) :: array ! (1) TYPEOF(array), INTENT(in), OPTIONAL :: identify TYPEOF(array) :: r INTERFACE ! (2) FUNCTION operation(x, y) RESULT(z) ! (3) IMPORT array ! (4) TYPEOF(array), INTENT(in) :: x, y ! (5) TYPEOF(array) :: z ! (6) END FUNCTION operation END INTERFACE INTEGER :: idx(rank(array)) ! (7) LOGICAL :: is_first is_first = .true. DO @idx = 1, ubound(array) ! (8) IF (is_first) THEN r = array(@idx) is_first = .false. ELSE r = operation(r, array(@idx)) END IF END DO IF (is_first) THEN IF (present(identity)) THEN r = identity ELSE ERROR STOP 'Argument IDENTITY expected for empty sequence' END IF END IF END FUNCTION reduce By the generic type declaration statement (1), ARRAY is specified as type-generic and rank-generic. The dummy procedure OPERATION is made explicit by the interface block (2). The intrinsic REDUCE allows only pure functions as its argument OPERATION, but this reduce allows impure functions as its argument OPERATION (3). This is because most procedures for the bigint type are impure. By using the IMPORT statement (4) and TYPEOF statements (5, 6), the arguments x and y and the result r of OPERATION have the same type of the argument ARRAY. The reference of rank(array) (7) and the use of rank-independent loops (24-143) (8) are the same as in 2.3. Following functions max_bi and sum_bi are examples of functions that define the procedure OPERATION: FUNCTION max_bi(x, y) RESULT(r) TYPE(bigint), INTENT(in) :: x, y TYPE(bigint) :: r IF (x >= y) THEN r = x ELSE r = y END IF END FUNCTION max_bi FUNCTION sum_bi(x, y) RESULT(r) TYPE(bigint), INTENT(in) :: x, y TYPE(bigint) :: r r = x + y END FUNCTION sum_bi These functions are not pure because each r%data is an allocatable array and allocated in the defined assignemnt statements. 3. Another use case =================== This section presents a use case that references the kind type parameter of generic dummy arguments. 3.1 Kind type parameter of generic dummy argument The function absmax takes a one-dimensional array of arithmetic types as an argument and returns the largest absolute value in the array elements. According to the definition of the intrinsic function ABS, the type and kind of the result y are the same as x when x is not a complex number, and the type is real and the kind is the same as x when x is a complex number. Since these type declaration statements for the result y cannot be unified, the generic subprogram must be divided into two depending on the type of x, as follows. GENERIC FUNCTION absmax(x) RESULT(y) TYPE(INTEGER(*),REAL(*)), INTENT(in) :: x(:) TYPEOF(x) :: y, abs_x INTEGER i, n y = -huge(y) DO i = 1, size(x) abs_x = abs(x(i)) IF (abs_x > y) y = abs_x END DO END FUNCTION absmax GENERIC FUNCTION absmax(x) RESULT(y) COMPLEX(*), INTENT(in) :: x(:) REAL(kind(x)) :: y, abs_x ! (1) INTEGER i, n y = -huge(y) DO i = 1, size(x) abs_x = abs(x(i)) IF (abs_x > y) y = abs_x END DO END FUNCTION absmax In the generic declaration type statement (1) of the latter subprogram, kind(x) is used to represent the kind parameter of the result y and the local variable abs_x. This is allowed in 25-120r1. 4. Requirements from use cases ============================== 4.1 Reference to the attributes of generic dummy argument Through the use cases shown in Sections 2 and 3, it became clear that there are various ways to refer to the attributes of generic dummy arguments other than TYPEOF, CLASSOF, and RANKOF. The patterns that actually appeared are as follows, and at least these must be supported in order to make generic subprograms useful. - type declaration statements - RANK(rank(x) + 1) as a rank-clause (not permitted) (2.2) - RANK(rank(x) - 1) as a rank-clause (not permitted) (2.4) - INTEGER :: v(rank(x)) (2.2, 2.3, 2.4, 2.5) - REAL(kind(x)) as a declaration-type-spec (3.1) - expression in the execution part - size(x, i) (2.2) - RANK (rank(x)) as a select-rank-case-stmt (2.3, 2.4) Where x is a generic dummy argument, v is a variable, and i is an integer variable. 4.2 "@" symbol notations It became clear that @ symbol notation was often needed in rank-generic subprograms. Not only multiple-subscript (R920) and multiple-subscript-triplet (R923) in Fortran 2023, rank-independent loop, which has been proposed in 24-143, is strongly expected. We also want to use @ symbol notation in ALLOCATE statement, as shown in sections 2.2 and 2.3. - ALLOCATE( r(@v1, n, @v2) ) (2.2) - ALLOCATE( r(@v1, @v2) ) (2.4) Where r is an allocatable variable, v1 and v2 are one-dimensional array variables, and n is a scalar integer. === END ===