There is a myth that with a strong and static typed language (Pascal) you cannot escape the strict type system.
There are several ways to escape the type system. In this article, for example, we will discuss the ability to pass in a variable typed parameter (dynamic type) into a procedure.
Obviously there will always be some extra work in order to get a strong/static typed language to obtain dynamic abilities - like ugly casts. But the point is that there are ways to escape the type system when you need to - and this should only come up in rare occasions anyway - so a programmer in my opinion is better safe by default, and only unsafe when he escapes the type system.
Variants are one way, of passing a variable (changeable) parameter into a procedure, but most people know about those. And variants aren't as powerful as what the article addresses below.
In object oriented code, you may see someone passing an abstract class in as a parameter if they want to fake dynamic typing. For example think about TStrings as a parameter versus TStringList. TStrings is not a real class - it's just an abstract class. The TStrings you pass in as a parameter could be a Memo.Strings or a TStringList.Strings. So the function/procedure accepting this abstract class as a parameter is similar to dynamic typing.
What if you are not trying to pass a class in as a parameter, or what if you do not want to go through the hassle of using an abstract class? What if you wish to send an integer, or a string, or even a file handle in to your procedure as one single variable parameter?
At this point you might say we are trying to emulate dynamic typing. For just a simple optional string/integer type as one parameter- it would be verbose and unnecessary to make an abstract class and store the integer or string in that class which overrides the abstract class. i.e. have one class with one field of the class an integer, and one other class as one field of the class being a string - then pass in the class to a function which can be a TIntegerClass or a TStringClass. This works, but a class would also require Free and Create bloat, along with unneeded complexity. Why have to create and free a class and make things overly complicated?
So what if an elite hacker wants develop a function/procedure that allows one to pass in a string, integer, text file type, or binary file type, or other types into a procedure as one single parameter? Dynamic typing? Not possible in Pascal? Rubbish... Maybe not possible in Java - but 100 percent possible in Pascal.
A Variant type in Pascal does not allow you to pass a file handle - so that idea is out. Plus, most people know about variants so I wouldn't have written this article if we were going to discuss boring variants. Try using a Text variable and assigning that text variable to a variant.
var
v: variant;
t: text;
begin
assign(t, 'test.txt');
v:= t; // can't do this
end.
It doesn't work. But with an untyped pointer and some wrappers around that untyped pointer, the possibilities are endless.
More power and stronger typing is available to you using an untyped pointer and wrappers (strongly typed) around your weak/dynamic untyped pointer. This way you get the best of both worlds - a strongly typed interface that allows you to pass in multiple dynamic types. It may seem a clash to be able to mix strong and dynamic typing, but see below and try to understand what is going on.
Note:
If you know about @addresses and TTypes vs PTypes and untyped pointers, you can make wrappers around your untyped pointer in order to make a clean strongly typed interface for the user of your unit to take advantage of.
In your unit interface which other programmers use - you will not want to use just PSomeTypes and Pointers as parameters - those should be optional but not required. So make a wrapper that allows @Type to be sent in as a PType parameter.
{$MODE OBJFPC} {H+}
{ We want to write a generic Writer procedure which either writes to a file handle,
writes to a string, writes to a binary file, writes to a pchar, writes to standard
out, writes to anything we wish. How do we make a general writer procedure using
only one parameter? }
interface
...
{ Note the AnyType parameter. It can be any type at all }
type
TWriterProc: procedure(input: string; var AnyType: pointer);
implementation
...
// dynamic writer proc.. but without using dynamic typing, without using variants,
// without using abstract classes!
procedure DynamicWriter(Proc: TWriterProc; var result: pointer);
begin
proc('whatever', result);
proc('..write text to anything, a string, bird, plane, file, pchar..', result);
proc(#10#13, result);
end;
(* -- Below, the Writer Procs that implement the dynamics of the system -- *)
{ write from a string into a pchar, we need to access the pchar as a param }
procedure PcharWriterProc(s: string; var APchar: pointer);
var
tmp2: pchar;
begin
if APChar <> nil then
begin
getmem(tmp2, length(pchar(APchar)) + length(s) + 1);
PCharCopy(pchar(APchar), tmp2);
PCharCat(pchar(s), tmp2);
freemem(APchar); // clear old contents
//.... etc. etc.
end;
//.. do more stuff to the pchar here
end;
{ write to stdout, stdout is available globally, so we pass NIL second param}
procedure StdOutWriterProc(s: string; var ANil: pointer);
begin
write(s); // write msg to stdout, ANil is a dummy
end;
{ write to an open file }
procedure TextFileWriterProc(s: string; var APTextFile: pointer);
begin
write(ptext(APTextFile)^, s); // write msg to open file
end;
(* -- now the wrappers that make the system easy to use -- *)
{ take some text from a Text File, write it to another different Text File }
procedure FileInToFileOut(inputfile: string; var AFile: ptext); overload;
begin
DynamicWriter(InputFile, @FileHandleWriter, pointer(AFile));
end;
// how would we pass a TEXT file in to the FileHandleWriter?
// Make a wrapper to convert a PText into a TEXT.
procedure FileInToFileOut(inputfile: string; var AFile: text); overload;
var
pt: ptext;
begin
pt:= @Afile;
// call overloaded procedure which accepts PText, not TEXT
FileInToFileOut(inputfile, pt);
end;
{ take some text from a Text File, write it to STDOUT }
procedure FileInToStdOut(inputfile: string);
var
p: pointer; // dummy!
begin
p:= NIL;
// proc below can except ANY Type as second parameter, even NIL
DynamicWriter(InputFile, @StdOutWriterProc, p);
end;
{ take some text from an existing Text File, write it to a string }
function FileInToString(inputfile: string): string;
var
tmp: pchar;
begin
result:= '';
// note: proc below can except ANY Type as third parameter
DynamicWriter(InputFile, @StringWriterProc, pointer(tmp));
result:= string(tmp); // cast makes a copy
end;
{ take something from an existing string, write it to a string }
function StrToOtherStr(input: string): string;
var
tmp: pchar;
begin
result:= '';
tmp:= nil;
// note: proc below can except ANY Type as third parameter
DynamicWriter(Input, @StringWriterProc, pointer(tmp));
result:= string(tmp); // cast makes a copy
end;
Note, that escaping the type system is only needed occasionally. Still, you are using a language with strong typing - just that you turn it off where needed, by using an untyped pointer - which is much safer than using a weakly/dynamically typed language all the time! I bet many dynamic/weak languages like PHP would like the ability to turn on strong typing when they need it.
Of course the above code addresses only part of dynamic typing features. It addresses passing in parameters to procedures or functions without any strong typing. You can pass a file handle, a string, an integer, or any type you can think of into the procedure. This is needed in cases like Writer procedures where you want to be able to write the same data to a string, file, or even a pchar or binary file - just by flicking the switch and letting the procedure know that if you are writing a to a binary file, call in the BinaryFileWriterProc procedure - if a text file, call in the TextFileWriterProc procedure.
This page does not obviously offer a full replacement for dynamic/weak typing combo like PHP offers (PHP is a dynamicly/weakly typed language). But that is exactly the point - you don't want to have all the disadvantages of dynamic/weak typing in a strongly typed language - you want to be able to use certain useful features of dynamic typing (or emulate them) where needed. So the above code addresses our need to sometimes escape the type system when we pass in parameters into a function - which is one useful feature of dynamically typed languages.
Creating your own dynamic typing (with strongly typed wrappers) is like inventing a mini dynamic type system in your program but only when you need it. Keep it safe - keep most of your program strongly typed, but when you really do need to break out of that type system remember that you can do so for certain features and qualities of dynamically typed languages.
And forget about using abstract classes all the time - that's a lot of code noise and overhead for the user of your units. When that user utilizing your unit needs to pass multiple simple/single types into a function like strings, integers and file handles, an abstract class is just overkill. Of course some times an abstract class is the correct choice as a parameter, such as TStrings vs TStringList. .
There are other escapes of the type system such as Array of Const and open arrays.. but that is beyond the scope of this article. Not to mention, an Open Array still does not allow you to pass in a file handle like we did above, and an Open Array is still more complex of a solution since the user of your unit has to figure out what this funny looking open array is (as opposed to just looking at the EXACT type that needs to be passed in - a string, integer, or file handle).
In the above code snippet example, if you study it long enough - you should see that you can write text to a string, pchar, text file, bird, airplane, or even a tennis ball or a cloud. Why would you want all this power, to write text to a file, or even an airplane? This is the disadvantage and advantage of dynamic typing.
You don't want to write text to an airplane if the airplane cannot accept text as an input! But a string, file handle, binary file, and pchar can all accept text into themselves! They are much different types from each other though, and you need to abstract or use some sort of "dynamics" in order to write text to those formats. Using a pointer to a procedure, and an untyped pointer, you can pass in multiple types and you can eat your cake and have it too.
Rule of Thumb
If you are an advanced programmer who knows all about pointers, make use of them. But do not allow your users of your source units to know about them. This is what the wrappers are for. You know what you are doing when you are using pointers, but assume your users know nothing about them.
This is also a lesson about why dynamic/weak typed languages can be dangerous - using a dynamic/weak typed language is similar to using pointers without typecasting. Yet most of the folks who use dynamic/weak typed languages don't have a clue what a pointer is. They think pointers are dangerous and probably know nothing about how a pointer works - because in fact their dynamic language they are using is like one big untyped pointer without typecasting.
This is why it is essential to have pointer knowledge whether you are using a scripting language or a low level language. Learning about advanced pointer techniques allows you to see why and when dynamic/weak combination typing is useful, and why and when strong/static combination typing is useful.
Rant:
Most Pascal programmers should agree that dynamic/weak typing is some times much less safer than strong/static typing (it is in now way perfect or the end of all problems, though), so it would be wise to use strong/static typing wherever possible. The dynamic/weak type advocates (PHP programmers and all) feel that strong/static typing requires too much typing on the keyboard. Wah, wah, poor baby - if that is the only argument -- my goodness. At least give a reasonable argument that doesn't make you look like a beginner sissy programmer. The amount of typing I do on the keyboard is the last thing I worry about while programming - except in rare cases like one time regexes in a search box, or really tiny web programs. Again, it's rare cases where we worry about the amount of typing needed on the keyboard. So reap the benefits of strong/static typing and only resort to dynamic/weak typing when you have to.
Additional notes:
When I say "dynamic/weak" typing I'm referring to languages which are dynamic and weak type style such as PHP. This does not mean "dynamic" and "weak" are two of the same things. In the article above, I'm just comparing dynamic/weak language like PHP to a strong/static language like Pascal - but there are other variations which I did not meation, such as dynamic/strong and weak/static mixture languages, such as Python/C/C++.
All languages are a mixture of sorts and there is controversy of what defines strong or weak or dynamic or static typing - but it is generally acceptable to say that PHP is a dynamic/weak language and Pascal is a strong/static language.
|