I found some nice ways to abuse the include files with compilers that support them. I want to implement this into the compilers without it being generics... it is something simpler. I realize it could be reinventing features that other languages already have, and it could be reinventing part of generics or part of templates, or part of polymorphism... but... I don't care. It's the concept that counts and there are benefits to my simple idea here.
It is an extremely simple concept and not some brand new complex paradigm that one has to learn. It is just a way or reusing the same algorithms for different "types". An "array of integer" versus an "array of string" has the same algorithm to add more items to it, so why rewrite the same procedure over and over again in a strongly statically typed language? Problem solved at compile time so speed is not sacrificed, with below solution.
(Note: Standard Pascal was even worse where you had to write your own algorithm for each array that had different bounds.)
This particular pattern should be categorized as Abuse... and I admit it... but it works, and is a proof of concept. It is not too obfuscated if you just look at the template.inc file... the other include files are data hiding and something the programmer shouldn't ever care about. With that being said, this idea can be implemented into a language natively... so it doesn't require this workaround with include files.
Some demos are here:
http://z505.com/download/lars-poly.zip http://z505.com/download/lars-poly2.zip
With a language that has an include file system, such as freepascal, a template looks like this... abusing the include files as our placeholders:
procedure add(const item: {$I T.inc}; var arr: {$I TArr.inc}); overload; var len: integer; begin // below is the same for EVERY type! Reuse, Reuse! len:= length(arr); setlength(arr, len+1); arr[len]:= item; end;The procedure contains declarations that can be programmed generically now. An array, of an unknown type in the above, is defined as our include file called T.inc, and TArr.inc as the second parameter (Array of T). We fill the include file with any types that we want... and they are no longer fixed since the type file is generic or it contains any type we want. The compiler knows them at compile time based on include file and IFDEF tricks. We can now program and design functions for arrays without reinventing the algorithm for each different type.
This trick is not just for arrays - I simply chose arrays to demonstrate the concept. The procedure implementation defined in "template.inc" maps Add() algorithm (add an item to an array) to several types magically.
This is how our include files look:
Include file: T.inc:
{$ifdef stringType}string{$endif} {$ifdef integerType}integer{$endif} // add more types here.. anything.. literally anythingInclude file: Tarr.inc
{$ifdef stringType}astrarr{$endif} {$ifdef integerType}intarr{$endif} // add more types here.. anything.. I use arrays in this demoInclude file: tpl.inc:
{$define integerType}{$I template.inc}{$undef integerType} {$define stringType} {$I template.inc}{$undef stringType} // add more template defines here for each type // with macro preproceessor, could be looped (i.e. more abuse, but not needed)Include file: template.inc:
procedure add(const item: {$I T.inc}; var arr: {$I TArr.inc}); overload; var len: integer; begin len:= length(arr); {$ifdef integerType} // can tune the procedure for specific types if item = 0 then begin writeln('Ha! You can''t add a zero.. '); exit; end; {$endif} {$ifdef stringType} if item = '' then begin writeln('No empty string allowed. '); exit; end; {$endif} setlength(arr, len+1); arr[len]:= item; end;A demo program showing the system in use:
program example; type astrarr = array of string; intarr = array of integer; // procedure add(const item: string; var arr: astrarr); // procedure add(const item: integer; var arr: intarr);} // procedure add(const item: anything; var arr: anythingarray);} // procedure add(const item: foo; var arr: fooarray);} // below template is what above functions end up being // but without duplicating the code for each type {$I tpl.inc} var array1: astrarr; array2: intarr; begin add('blah blah', array1); add(123, array2); writeln('Item 0 in array1: ', array1[0]); writeln('Item 0 in array2: ',array2[0]); add('', array1); // this will give you a nice warning ;-) add(0, array2); // ... same here writeln('Length: ', length(array1)); writeln('Length: ', length(array2)); add('Foo Bar', array1); // does it let us?? sure add(505, array2); // does it let us?? sure writeln('Item 1 in array1: ', array1[1]); writeln('Item 1 in array2: ', array2[1]); writeln('Length: ', length(array1)); writeln('Length: ', length(array2)); readln; end.
An interesting find that seems to be solving the same issue built into the language:
"ANYREC and ANYPTR
Each base record is implicitly regarded as an extension of the new abstract standard type ANYREC, even if it is declared without explicit base type. ANYREC is an empty record that forms the root of all record type hierarchies. ANYPTR is a new standard type that corresponds to a POINTER TO ANYREC.
These new types make it easier to achieve interoperability between independently developed frameworks, by allowing completely generic parameters. ANYREC = ABSTRACT RECORD END; ANYPTR = POINTER TO ANYREC; PROCEDURE (a: ANYPTR) FINALIZE-, NEW, EMPTY;"
http://npt.cc.rsu.ru/user/wanderer/ODP/component_pascal/component_pascal.html
However, I'll note that this only allows generic arrays... the include file hack could be used for any type and for more creative ideas and concepts.
See also: ParametricPolymorphism