J3/02-294r1 Date: 15 November 2002 To: J3 From: Aleksandar Donev Subject: Deficient handling of objects in F2x, OR Default CREATE, COPY and CLONE for polymorphic objects Reference: J3-007R3, J3/02-295 ______________________________________ Summary ______________________________________ An OOP language should provide default COPY, CLONE and CREATE operators/methods for polymorphic objects. This way objects, and not just references to them, can be manipulated inside (endogenous) data-structures and other objects-that-manipulate-objects. The critical point is that these need to be accessible via the object itself, even if the type of the object is not! CLONE is implemented via SOURCE in ALLOCATE. I propose to implement CREATE via a new MOLD argument in ALLOCATE, and COPY by modifying the meaning of intrinsic assignment for polymorphic variables to work on the actual and not on the declared types. I illustrate the urgent need for these with a very elementary example of implementing a generic stack via arrays. Comments and suggestions on this code are very welcome. The code should really be written using types parameterized with types (a.k.a. templated types), but this is beyond repair for this revision of the language. ______________________________________ Motivation ______________________________________ In Fortran 2002 (and other OOP languages), polymorphic objects are implemented via dynamic references to them, i.e., POINTER or ALLOCATABLE polymorphic variables. Fortran 2002 provides excellent OOP facilities for manipulating these polymorphic references to objects (see my comments on lack of type specification and overuse of SELECT TYPE though). Thus implementing exogenous dynamic data-structures is supported well. But the language has profound deficiencies when dealing with the objects themselves, so that implementing endogenous structures (and these are very common in scientific programming), is all but impossible. Eiffel is an OOP language whose design I value greatly (though I would not trade it for Fortran, of course!). It provides three default methods for any object: CREATE, COPY and CLONE These three are fundamental operations needed to manipulate objects (and not just references to them) directly. Note that these methods are accessible through the object itself, without access to its type. In the current version of the Fortran draft, CREATE (structure constructors) and COPY (assignment inside SELECT TYPE) are only accessible if the type is accessible. CLONE is at present the only one provided in F2002 via the SOURCE argument to ALLOCATE. I feel strongly that the other two need to be provided also. CREATE can most easily be provided by adding a MOLD argument to ALLOCATE similar to the SOURCE argument, which would only give the type of the allocated data, but not the contents. This would allow one to allocate an array of a given type given only a scalar of that type, which is needed, for example, to design custom allocators for various dynamic data structures. COPY is trickier. I think it should be provided by modifying the meaning of intrinsic assignment for polymorphic variables to work on the actual and not on the declared types. I propose two possible interpetations in the case when the dynamic types of the left and right hand sides defer, and propose to accept the simpler of the two choices. This also requires a modification of the way base component selection works for polymorphic objects, so that the current meaning of intrinsic assignment can be used if needed (for efficiency). This will also be proposed in a separate paper J3/02-295. ______________________________________ Example: Array-based stack ______________________________________ A homogeneous stack can be implemented via an array. The allocation and deallocation of the array should be handled inside the stack implementation. But at present this is not possible in Fortran since the type of the allocated array is dynamic and cannot be given to ALLOCATE. Furthermore, when the stack fills up, the contents of the array should be copied to a larger array (reallocation). Again, this copy is very hard to do in Fortran 2002, since intrinsic assignment works on the declared types. One must therefore ask clients of the Endogenous_Stack class to themselves implement defined assignment and creation. This is unneccessary and very cumbersome for such a common and elementary application. Here is how the stack would be implemented in my modified Fortran 2002. Later I will write this in what I think should be in Fortran 200+ (i.e., the next revision), using generic objects (templates in C++, unconstrained generic type parameters in Eiffel). MODULE Endogenous_Stacks ! For homogeneous stacks only! ! NOTEs: ! CREATE, COPY and CLONE are all needed and used in this example. ! Data is not carefully encapsulated--assume the user is trustable! ! Memory allocation failure is also *not* checked! TYPE, EXTENSIBLE, PUBLIC :: Endogenous_Stack CLASS(*), POINTER :: stack_mold=>NULL() ! What is this a stack of? INTEGER :: actual_size=0 INTEGER :: expected_size=1000 CONTAINS PROCEDURE, DEFERRED, PASS(stack) :: PushOnStack, PopOffStack END TYPE Endogenous_Stack ABSTRACT INTERFACE FUNCTION PushOnStack(stack,element) RESULT(success) CLASS(Endogenous_Stack), INTENT(INOUT) :: stack CLASS(*), INTENT(IN) :: element LOGICAL, INTENT(OUT) :: success END FUNCTION PushOnStack FUNCTION PopOffStack(stack,element) RESULT(success) CLASS(Endogenous_Stack), INTENT(INOUT) :: stack CLASS(*), INTENT(OUT) :: element LOGICAL, INTENT(OUT) :: success END FUNCTION PopOffStack END INTERFACE END MODULE Endogenous_Stacks MODULE Endogenous_Array_Stacks USE Endogenous_Stacks TYPE, EXTENDS(Endogenous_Stack), PUBLIC :: Endogenous_Array_Stack CLASS(*), DIMENSION(:), ALLOCATABLE :: storage ! Array-based implementation CONTAINS PROCEDURE, PASS(stack) :: PushOnStack=>PushOnArrayStack, & PopOffStack=>PopOffArrayStack END TYPE Endogenous_Array_Stack CONTAINS FUNCTION PushOnArrayStack(stack,element) RESULT(success) CLASS(Endogenous_Stack), INTENT(INOUT) :: stack CLASS(*), INTENT(IN) :: element LOGICAL, INTENT(OUT) :: success CLASS(*), DIMENSION(:), ALLOCATABLE :: temp_storage ! For reallocation IF(.NOT.ASSOCIATED(stack_mold)) THEN WRITE(*,*) "No mold for stack!" success=.FALSE. RETURN ELSE IF(.NOT.SAME_TYPE_AS(stack_mold,element)) THEN WRITE(*,*) "Wrong argument type for stack push" success=.FALSE. RETURN END IF END IF ! No allocation error handling here for now: IF(.NOT.ALLOCATED(stack%storage)) & ALLOCATE(stack%storage(stack%expected_size), MOLD=stack_mold) ! Use the MOLD argument to CREATE the array stack%actual_size=stack%actual_size+1 IF(SIZE(stack%storage)