BCS Logo
BRITISH COMPUTER SOCIETY Fortran Specialist Group

Fortran 2000
Programme
The Group
Comment
Overview
Data handling
Exceptions and C
Derived types
Report

About Us
FSG Home page
Joining the Group
Contact details

Important Disclaimer

Object-oriented Enhancements

Malcolm Cohen
The Numerical Algorithms Group Ltd.

Please note: this HTML file has been constructed directly from the slides used in the presentation.


Overview

Basic object-oriented features

Advanced features:


Back to the top

Type extension

Takes an existing type and extends it by adding components.

  • Only "extensible" types can be extended (all extended types are "extensible").
  • Base type need not have any components.
  • Extension need not add any components.
  • Base type part access via base type name.
  • Extension inherits all components of base type.

Type extension examples

TYPE,EXTENSIBLE :: base
END TYPE

TYPE,EXTENDS(base) :: extended
END TYPE

TYPE,EXTENSIBLE :: point
  REAL x,y
END TYPE

TYPE,EXTENDS(point) :: colour_point
  REAL r,g,b
END TYPE

TYPE(point) a        ! contains: x,y
TYPE(colour_point) b ! contains: x,y,r,g,b,point

b%x = 0     ! sets x component of b
b%point = a ! assigns a to point component of b

The "parent component" (e.g. b%point) is "inheritance associated" with the inherited components (e.g. b%x and b%y). Thus b%point%x is a long-winded way of writing b%x.

Type extension notes

  • Extensible types cannot be SEQUENCE types; so that there is a unique definition of each extensible type (and thus only one copy of the type information).
  • The type names cannot be the same as any of their components.
  • Extensible types may be further extended in other program units.
  • The "parent component" may have an explicit accessibility specification, and/or explicit initialisation, e.g.
TYPE,EXTENDS(PRIVATE::point=point(0.0,0.0)) :: silly_point
  REAL silliness
END TYPE

Back to the top

Polymorphic variables

Polymorphic variables are variables which have not only a declared type but also a dynamic type which may vary at runtime. They come in two flavours:

CLASS(type)
These variables can have a dynamic type which is TYPE(type) or any type extended from that.
CLASS(*)
These "unlimited polymorphic" variables can have a dynamic type which is any extensible type.

Polymorphic variables only give direct access to the components of their declared type. Any additional components possessed by their extended type are not directly accessible as this would not be safe ("Type selection" q.v.).

Polymorphic variables continued

Polymorphic variables must be:

  • dummy arguments,
  • pointers or
  • allocatables.

Thus "normal" variables must be of fixed type.

Also, polymorphic arrays are homogenous (the elements are all of the same dynamic type).
Therefore the type signature is per variable, not per array element; and array index calculation remains a linear transformation.

A function can return a polymorphic pointer or allocatable (if the function result variable is polymorphic).

Polymorphic variable example

MODULE point_module
  TYPE,EXTENSIBLE :: point
    REAL x,y
  END TYPE
  TYPE,EXTENDS(point) :: colour_point
    REAL r,g,b
  END TYPE
CONTAINS
  REAL FUNCTION distance_between(a,b)
    CLASS(point),INTENT(IN) :: a,b
    distance_between = &
      sqrt((a%x-b%x)**2+(a%y-b%y)**2)
  END FUNCTION
END

The procedure distance_between can operate not only on variables of TYPE(point) but also on variables of TYPE(colour_point) and indeed on variables of any type extended from TYPE(point) which the user may define.

Associating polymorphic variables

Polymorphic variables may only become associated if it is "type-safe", i.e. where the declared types ensure that there is no possibility of a runtime error.

This applies both to argument association and to pointer association.

  SUBROUTINE s_point(x)
    CLASS(point) x
    ...
  END
  SUBROUTINE s_colour_point(y)
    CLASS(colour_point) y
    ...
  END
  ...
  TYPE(point) a
  TYPE(colour_point) b
  ...
  CALL s_point(a)        ! ok, both CLASS(point)
  CALL s_point(b)        ! ok, colour_point extends point
  CALL s_colour_point(a) ! not ok, not an extension
  CALL s_colour_point(b) ! ok, both CLASS(point)

Changing TYPE to CLASS above would not alter the "ok/not ok" status.

Associating polymorphic pointers

Polymorphic pointers have the same restrictions on pointer association as polymorphic variables have on argument association.

  CLASS(point),POINTER :: x,a
  CLASS(colour_point),POINTER :: y,b
  x => a   ! ok, both point
  x => b   ! ok, colour_point extends point
  y => a   ! not ok, not an extension
  y => b   ! ok, both colour_point

Type enquiry

Two intrinsic functions are provided which perform type enquiries.

SAME_TYPE_AS(A=x,B=y)
Returns .TRUE. if and only if the dynamic types of x and y are the same.

EXTENDS_TYPE_OF(A=x,MOLD=y)
Returns .TRUE. if and only if the dynamic type of x is the same as or an extension of the dynamic type of y.

  • The dynamic type of a disassociated pointer or unallocated allocatable is its declared type.
  • Pointers with an undefined association status are not allowed as arguments to these functions.
  • The functions are not tremendously useful because simply knowing (at runtime) that an object has a particular extended type does not give one access to its extended components!

Back to the top

Type selection

Type selection is a structured type enquiry for polymorphic entities, providing direct access to the extended components of the dynamic type.

  SUBROUTINE print_colour(x)
    CLASS(point) :: x
    SELECT TYPE(q=>x)
    TYPE IS (point)
      ! In here, q is TYPE(point).
      !
      PRINT *,'No colour'
    CLASS IS (colour_point)
      ! In here, q is CLASS(colour_point)
      !
      PRINT *,'Colour is',x%r,x%g,x%b
    CLASS DEFAULT
      ! In here, q is CLASS(point)
      !
      PRINT *,'Unknown colour status'
    END SELECT
  END

Type selection notes

    SELECT TYPE(q=>x)
    TYPE IS (point)
      ...
    CLASS IS (colour_point)
      ...
    CLASS DEFAULT
      ...
    END SELECT

  • The "q=>" part may be omitted if the expression is a simple name (like x); in this case the "associate-name" is the same as the name in the expression.
  • The ordering of the "type guards" is unimportant. If more than one guard would be satisfied by the dynamic type, the most specific guard is chosen.

Back to the top

Dynamic type allocation

The dynamic type for a polymorphic variable may be explicitly specified on the ALLOCATE statement. (If omitted, it allocates an object of the declared type.)

  CLASS(point),ALLOCATABLE :: x,y
  ...
  ! Allocate x as TYPE(point)
  ALLOCATE(x)
  !
  ! Allocate y as TYPE(colour_point)
  ALLOCATE(TYPE(colour_point)::y)

Back to the top

Cloning

In an ALLOCATE statement, it is possible to take the dynamic type and value of another variable for the new allocation.

  CLASS(point) x
  CLASS(point),POINTER :: copy_of_x
  ALLOCATE(copy_of_x,SOURCE=x)

Example: list copying

This is an example of a very simple heterogeneous list type; cloning can be used to implement a "list copying" procedure that will work on these lists no matter what type each individual element is.

TYPE,EXTENSIBLE :: slist ! Singly-linked list
  CLASS(slist),POINTER :: next => NULL()
END TYPE
TYPE,EXTENDS(slist) :: real_item
  REAL value
END TYPE
TYPE,EXTENDS(slist) :: integer_vector_item
  INTEGER,ALLOCATABLE :: vector(:)
END TYPE
...
FUNCTION copy_of_list(x)
  CLASS(slist),POINTER :: x
  ALLOCATE(copy_of_list,SOURCE=x)
  IF (ASSOCIATED(x%next)) THEN
    copy_of_list%next => copy_of_list(x%next)
  END IF
END

Back to the top

Type-bound procedures

  • Procedures bound to the type of an object.
  • Invoked through an object (like procedure pointer components).
  • Invoking object may be passed "by magic".
  • Always accessible whenever an object of the type is accessible.
  • The type need not be extensible.
  • For extensible types, provides dynamic dispatch.
  • May be inherited or overridden.
  • Control over overriding (limits dynamic dispatch).
  • Compile-time type safety.
  • Binding is to a module procedure or an external procedure with an explicit interface.

Magic object passing

The object through which a procedure is invoked may be passed automatically to the invoked procedure without having to include the object in the argument list.

This applies to "object-bound procedures" (procedure pointer components) as well as to type-bound procedures.

By default, the "passed-object dummy argument" is the first argument of the procedure; it must be an ordinary scalar dummy variable, and any non-kind type parameters must be assumed.

Thus, in

CALL object%procedure(arguments...)

the variable object will normally be passed to the selected procedure as an extra actual argument.

Magic object passing: example

  TYPE callback_with_data
    PROCEDURE(data_cb_proc),PASS(cb_record) :: proc
    REAL,ALLOCATABLE :: data(:)
  END TYPE
  ABSTRACT INTERFACE
    SUBROUTINE data_cb_proc(cb_record)
      IMPORT callback_with_data
      TYPE(callback_with_data) cb_record
    END SUBROUTINE
  END INTERFACE
  ...
  TYPE(callback_with_data) cb
  ...
  CALL cb%proc ! Passes cb itself as the first dummy arg

  • "(callback_record)" may be omitted if it is the first dummy argument.
  • If the type is extensible, it must be polymorphic.
  • If this feature is not wanted, NOPASS must be specified.

Type-bound procedures: Example 1

MODULE randomness
  TYPE random_stream
    ...
  CONTAINS
    PROCEDURE,NOPASS :: hello => show_copyright
    PROCEDURE :: current => current
    PROCEDURE,PASS(this) :: print_a_value
  END TYPE
CONTAINS
  SUBROUTINE show_copyright
  END SUBROUTINE
  REAL FUNCTION current(self)
    TYPE(t) self
    ...
  END FUNCTION
  SUBROUTINE print_a_value(unit,this)
    INTEGER unit
    TYPE(t) this
    ...
  END SUBROUTINE
END

Type-bound procedures: Example 1...

PROGRAM example
  USE randomness
  TYPE(random_stream) x
  CALL x%hello
  a = x%current()
  CALL x%print_val(6)
  !
  ! The above is equivalent to:
  !
  !   CALL show_copyright
  !   a = current(x)
  !   CALL print_val(6,x)
  !
  ! apart from accessibility considerations.
END

Type-bound procedures: Example 2

MODULE data_module
  PRIVATE
  PUBLIC datatype
  TYPE,EXTENSIBLE :: datatype
    REAL value1,value2
  CONTAINS
    PROCEDURE print_data => print_data_1
    PROCEDURE score
    PROCEDURE,NON_OVERRIDABLE :: prequalified
  END TYPE
CONTAINS
  SUBROUTINE print_data(this,unit)
    CLASS(datatype) this
    INTEGER unit
    WRITE(unit,*) this%value1,this%value2
  END SUBROUTINE
  REAL FUNCTION score(self)
    CLASS(datatype) self
    score = self%value1*0.35 + self%value2*0.65
  END FUNCTION
  LOGICAL FUNCTION prequalified(this)
    CLASS(datatype) this
    prequalified = this%value1>32
  END FUNCTION
END

Type-bound procedures: Example 2...

MODULE annotated_data_module
  USE data_module
  PRIVATE
  PUBLIC datatype,annotated_datatype
  TYPE,EXTENDS(datatype) :: annotated_datatype
    CHARACTER(:),ALLOCATABLE :: annotation
  CONTAINS
    ! Inherit score and prequalified
    ! Override print_data procedure
    PROCEDURE print_data
  END TYPE
CONTAINS
  SUBROUTINE print_data(this,unit)
    CLASS(annotated_datatype) this
    INTEGER unit
    WRITE(unit,*,ADVANCE='no') this%annotation
    CALL this%datatype%print_data(unit)
  END SUBROUTINE
END TYPE

Type-bound procedures: Example 2...

MODULE revised_data_module
  USE data_module
  PRIVATE
  PUBLIC datatype,revised_datatype
  TYPE,EXTENDS(datatype) :: revised_datatype
    REAL additional_value
  CONTAINS
    PROCEDURE score
    PROCEDURE print_data
  END TYPE
CONTAINS
  REAL FUNCTION score(self)
    CLASS(revised_datatype) self
    score = &
      MAX(this%value1,this%revision)*0.3 + &
      MAX(this%value2,this%revision)*0.6
  END FUNCTION
  SUBROUTINE print_data(this,unit)
    ...
  END SUBROUTINE
END

TBP: dynamic dispatch

For variables of fixed type, dispatch is static.

  TYPE(datatype) x
  TYPE(annotated_datatype) y
  ...
  CALL x%print_data ! data_module:print_data_1
  CALL y%print_data ! annotated_data_module:print_data

For polymorphic variables, dispatch is dynamic.

  SUBROUTINE process(data_item)
  CLASS(datatype) data_item
  ...
  ! This calls the corresponding procedure for the
  ! dynamic type of "data_item".
  !
  CALL data_item%print_data(6)

Dispatch is static for non-overridable procedures.

  CLASS(data_item) x
  ...
  PRINT *,x%prequalified()

Conditions for overriding

There are strict requirements on overriding a type-bound procedure; the following characteristics of the bindings must match:

  • Possession of a passed-object dummy argument (and which one).
  • Being elemental.
  • Being a subroutine or function.
  • The number of dummy arguments.
  • The names of the dummy arguments.
  • The characteristics of the dummy arguments, other than the type of the passed-object dummy argument.
Additionally, a pure procedure may only be overridden by another pure procedure, and a public binding may only be overridden by another public binding.

These rules ensure that compile-time argument checking can be done for type-bound procedures.

Back to the top

Generic type-bound procedures

Just like non-generic type-bound procedures, but invocation is generic.
Or: just like (non-type-bound) generic procedures, but invocation is via the type of an object.

  • named generics
  • operators, assignment
  • user-defined derived-type i/o
  • always accessible whenever an object of the type is accessible

Named generics

MODULE cart_module
  TYPE car_t
    ...
  CONTAINS
    GENERIC :: steer => set_direct, goto_posn
  END TYPE
CONTAINS
  SUBROUTINE set_direct(car,bearing)
    TYPE(car_t) car
    REAL bearing
    ...
  END SUBROUTINE
  SUBROUTINE goto_posn(car,xpos,ypos
    TYPE(car_t) car
    REAL xpos,ypos
    ...
  END SUBROUTINE
END
  USE cart_module
  ...
  TYPE(car_t) x
  ...
  CALL x%steer(180.0)     ! set direction
  CALL x%steer(10.0,20.0) ! go to position

Operators and Assignment
(and user-defined derived-type i/o)

MODULE mycomplex_module
  TYPE mycomplex
    REAL modulus,magnitude
  CONTAINS
    GENERIC :: OPERATOR(+) => mc_add_r, ...
  END TYPE
CONTAINS
  TYPE(mycomplex) FUNCTION mc_add_r(a,b)
    TYPE(mycomplex),INTENT(IN) :: a
    REAL,INTENT(IN) :: b
    mc_add_r = ...
  END FUNCTION
  ...
END
  • For operators, assignment and derived-type i/o, the generic binding cannot have the NOPASS attribute (i.e. it can only be invoked through an object of the type which is then passed to it as a dummy argument).

Generics and Inheritance

Generic type-bound procedures are inherited in type extension.

A generic may be overridden in extension; i.e. (like non-generics) a new procedure replaces the old one. Overriding occurs when the generic-spec (viz the generic name, operator, etc.) is the same and when the conditions for overriding are satisfied.

A generic may also be extended in extension; that is, new procedures may be added to the generic set. (This only affects the new type and any of its extensions). Extension occurs when the generic-spec is the same, but the conditions for overriding are not satisfied; in this case the generic disambiguation rules must be satisfied.

Type extension and type parameters

  • An extensible type may have type parameters.
  • Type parameters are inherited through type extension.
  • Additional type parameters may be added in type extension.

  TYPE,EXTENSIBLE :: t(wp)
    INTEGER,KIND :: wp
    REAL(wp) value
  END TYPE

  TYPE,EXTENDS(wp) :: t2(len)
    INTEGER,KIND :: len
    CHARACTER(len) name
  END TYPE

  TYPE(t(KIND(0d0))) double_x
  TYPE(t2(KIND(0.0),37)) named_single_x

Cleanup

Final Subroutines

    TYPE t
       REAL,POINTER :: p(:)
    CONTAINS
       FINAL :: clean_t_s, clean_t_a1
    END TYPE

A matching (by rank and kind type parameters) final subroutine is invoked immediately prior to "destruction" of a variable.
No match => no invocation.

For nested or extended types, final subroutines are invoked "outside-in".

Back to the top


OO Summary

Single inheritance - type extension

Dynamic types - polymorphic variables

Safe type enquiry - SELECT TYPE

Dynamic dispatch (methods) - type-bound procedures

Operation "packaging" - generic type-bound procedures

Finalisation - final subroutines


Valid HTML 4.01! Comments on this or any other of the Group's pages should be sent by e-mail to the BCS FSG Web Editor, Peter Crouch, at pccrouch@bcs.org.uk



© Copyright The British Computer Society