Skip to content
/ any Public

A user-defined type for mimicking procedures that can return different types

License

Notifications You must be signed in to change notification settings

degawa/any

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

any type

A user-defined type for mimicking procedures that can return different types.

Motivation

In Fortran, the generic name allows to switch the function to be called depending on the types of function arguments. On the other hand, it is impossible to define functions that return values of different types with the same name and argument type. If the return value is defined as class(*), allocatable, it is possible to return values of different types. However, the caller must receive the return value as class(*), allocatable and convert it to a specific type.

The any_type provided in this repository aims to replace class(*), allocatable and mimic the return of different types from functions. The any_type simplifies the procedure implementation for retrieving values from linked lists or hashmaps containing different types.

Getting started

Requirements

Due to the use of object-oriented programming and rank-15 arrays, a recent compiler is required to use any_type. The compilers and versions listed below have been used to develop the any.

  • Modern Fortran compiler
  • Fortran Package Manager (fpm) 0.7.0 alpha
  • fassert (Optional)
    • used only for the unit testing. If you do not need it, delete the entry in the dependencies section in fpm.toml.

Get the code

To get the code, execute the following commnad:

git clone https://github.com/degawa/any.git
cd any

Build with fpm

To build the library using fpm, execute the following command:

fpm build

Reference from your project

Add the following use statement to modules or procedures that use any_type.

use :: any_t

Note that the project name is any, but the module name is any_t.

Reference as a fpm project's dependency

To use any_type in your fpm project, add the following to the fpm.toml.

[dependencies]
any = {git = "https://github.com/degawa/any.git"}

Usage

Using the any_type is simple, thanks to the features of object-oriented programming.

  • Define the return value type as any_type in the function that returns different types.
  • Call the function and assign the function's return value to a variable.

For example, depending on the argument's value, the max_of function returns an integer or real.

function max_of(type_name)
    implicit none
    character(*), intent(in) :: type_name
    type(any_type) :: max_of

    select case (type_name)
    case ("int32")
        max_of = huge(0)
    case ("real32")
        max_of = huge(0.)
    end select
end function max_of

Calls the function and assigns the result to a value. int_max has 2147483647 and real_max has 3.40282347E+38.

integer(int32) :: int_max
real(real32) :: real_max

int_max  = max_of("int32")  ! 2147483647
real_max = max_of("real32") ! 3.40282347E+38

The implementation above using the any_type is significantly more straightforward than class(*), allocatable.

function max_of(type_name) result(val)
    implicit none
    character(*), intent(in) :: type_name
    class(*), allocatable :: val

    select case (type_name)
    case ("int32")
        allocate (val, source=huge(0))
    case ("real32")
        allocate (val, source=huge(0.))
    end select
end function max_of
integer(int32) :: int_max
real(real32) :: real_max
class(*), allocatable :: retval

retval = max_of_class("int32")
select type (retval)
type is (integer(int32))
    int_max = retval ! 2147483647
end select

retval = max_of_class("real32")
select type (retval)
type is (real(real32))
    real_max = retval ! 3.40282347E+38
end select

When the return value is passed directly to a print or write statement or passing it to a procedure, the conversion to the specific type is required.

print *, as_int32(max_of("int32")), as_real32(max_of("real32"))

The result is undefined when

  • Assigning the return value to a variable different from the function's return type.
  • Converting to a different type from the function's return type.

Users have the responsibility to match the types and ranks. Using a any_type other than the function's return type is not recommended.

Supported types and extension

The any_type supports the intrinsic types (integer(int8), integer(int16), integer(int32), integer(int64), real(real32), real(real64), real(real128), complex(real32), complex(real64), complex(real128), logical, and character(*)) and arrays of ranks 1 to 15 of their types. Note that an array must have the allocatable attribute when assigning any_type to the array.

The conversion functions for their types are defined:

  • as_int8(), as_int8_rank[1-15]()
  • as_int16(), as_int16_rank[1-15]()
  • as_int32(), as_int32_rank[1-15]()
  • as_int64(), as_int64_rank[1-15]()
  • as_real32(), as_real32_rank[1-15]()
  • as_real64(), as_real64_rank[1-15]()
  • as_real128(), as_real128_rank[1-15]()
  • as_complex32(), as_complex32_rank[1-15]()
  • as_complex64(), as_complex64_rank[1-15]()
  • as_complex128(), as_complex128_rank[1-15]()
  • as_logical(), as_logical_rank[1-15]()
  • as_char(), as_char_rank[1-15]()

Note that the left-hand side array does not have to be allocatable when using conversion functions, unlike assigning any_type to arrays via the assignment operator.

type(any_type) :: any
integer(int8), allocatable :: val_a(:)
integer(int8) :: val_s(15)
...
val_a = any                ! allowed
val   = any                ! not allowed
val   = as_int8_rank1(any) ! allowed

To support types other than the intrinsic types, Extend the any_type and add a type-bound procedure for assignment. Also, implement the conversion function if necessary. The following shows an example of supporting a user-defined type vector_2d.

    type, public :: vector_2d
        real(real32) :: x
        real(real32) :: y
    end type vector_2d
    type, public, extends(any_type) :: any_ext
    contains
        procedure, public, pass(rhs) :: assign_to_vec2
        generic :: assignment(=) => assign_to_vec2
    end type any_ext

contains
    subroutine assign_to_vec2(lhs, rhs)
        use, intrinsic :: iso_fortran_env
        implicit none
        type(vector_2d), intent(inout) :: lhs
        class(any_ext), intent(in) :: rhs

        select type (val => rhs%get_component()); type is (vector_2d)
            lhs = val
        end select
    end subroutine assign_to_vec2

    function as_vector2d(this) result(val)
        implicit none
        class(any_ext), intent(in) :: this
        type(vector_2d) :: val
        val = this
    end function as_vector2d

Note that the user-defined type must be correctly assignable by the assignment operator.