I realise this discussion is now 8 months old but I think it warrants some clarification, if only for posterity.
Post by Anton ShepelevPost by Martin BrownWhat do you have against declaring the routines as
taking an open array of TYPE ?
My original design to write a generic sortine mod-
ule, which can be used any kind of array-like data.
It seems to me that you are confusing "generic" and "dynamically typed".
The term generic programming has a very specific meaning. The terminology can be a bit misleading. A better name would be template programming because that is what it actually comes down to.
When you write a generic library, you are writing a template with a type placeholder. When you are using the generic library for a given type, a template engine built into your compiler will do template expansion by replacing the type placeholder with the actual type to create a new library specific to the given type. This is called specialisation but in reality it is simply a template expansion.
The benefit of generic programming is that you only need to design the code once. The template engine will then rewrite it for you when you use it. The disadvantage is that every time you expand a template with a type it hasn't been used with yet, you are duplicating the code. You get one copy of your library for each type you use it with. However, since each expanded library is specific to one type, it is perfectly type safe as long as the language is type safe.
What you described in your casting array example is NOT generic code.
Instead of using a template and expand it to derive specialised libraries, your approach uses a single copy of code that aims to act on multiple different types at runtime. This is dynamic typing, even though in your example it is a very crude form of dynamic typing and without any type safety whatsoever. It is not generic programming however.
As others have pointed out, ISO Modula-2 defines a generic programming extension and there are two compilers that support this. This extension is proper generic programming, a template expansion facility that specialises the code at compiler time.
If you are working with platforms where there is no M2 compiler that supports generic programming, you can easily roll your own template engine to expand placeholders and use that on your generic code as a pre-processor prior to compilation.
We do that in Modula-2 R10 where placeholders are enclosed with a leading and traling ##.
PROCEDURE SortArray ( VAR a : ARRAY OF ##ComponentType##; ascending : BOOLEAN );
You call the template engine with a type to be used for the placeholder, say
ComponentType=INTEGER
and the example above will expand to
PROCEDURE SortArray ( VAR a : ARRAY OF INTEGER; ascending : BOOLEAN );
In Modula-2 R10 the compiler supports this through a directive that can be used within the import section of a module to feed placeholder/translation pairs to the template engine and launch it (as it is an external utility).
IMPORT SuperLongInteger;
GENLIB SortSuperLongIntArraySort FROM SortArrays FOR
ComponentType = SuperLongInteger
END; (* GENLIB *)
IMPORT SuperLongIntArraySort;
But this is merely convenience. You can do this manually with a simple template expansion engine for any kind of code and any kind of language. All you need to do is make sure that the delimiters that mark the placeholders are not used otherwise in the language for which you are writing templates.
Note, it is entirely unnecessary to do type checking on generic templates as is done in ISO Modula-2 because the compiler will do type checking on the expanded source after expansion anyway.
However, in your example, you were trying to do dynamic typing. If this is your preference, you can do that, too. But you need some form of OOP with the ability to do runtime type testing.
In Modula-2 R10 we use the Oberon model. In Oberon, Wirth replaced variant record types with extensible record types. This means, you can define a new record type based on an already existing record type.
(* Root Type *)
TYPE Number = RECORD ( NIL )
...
END; (* Number *)
(* Sub Type *)
TYPE Integer = RECORD ( Number )
value : INTEGER
END; (* Integer *)
TYPE Cardinal = RECORD ( Number )
value : CARDINAL
END; (* Cardinal *)
etc etc
A type extension is compatible with the type it extends. Thus you can pass instances of both Integer and Cardinal to parameters of type Number.
A CASE statement within the procedure is then used to determine at runtime what the actual type is before handling the value field ...
PROCEDURE Foo ( n : Number );
BEGIN
CASE n OF
| Integer : (* code to handle integer values *)
| Cardinal : (* code to handle cardinal values *)
...
END (* CASE *)
END Foo;
For more detail, check this article:
https://github.com/m2sf/m2bsk/wiki/Object-Oriented-Programming
You can do this in Oberon (with some minor differences in syntax). ISO Modula-2's object oriented extension is not dynamically typed, so you would have to manually add a type field to the class, maintain it and check that.
hope this clarifies
benjamin