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
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