I like Pascal, and it is in fact the only language I'm thoroughly familiar with. But sometimes I wonder how it seems to make simple tasks more difficult than they ought to be. Here is an example, taken from my unit for complex numbers. Don't get me wrong, the unit works and served me well over the years, but there are formal deficiencies which I struggle to resolve when aiming for effective code and a straightforward syntax.
We have
type complex = record re,im: double; end; // in fact I use advanced records but irrelevant here
procedure Add (const a,b: complex; out c: complex); inline;
begin
...
end;
operator + (const a,b: complex): complex; inline;
begin
Add (a,b,result);
end;
I chose this because operators, while convenient, are much slower; the infix notation requires the compiler to create a temporary and then copy it. Unfortunately the compiler uses the slow REP MOVSL for copying, as soon as the block size is larger than 8 bytes. For timing measurements see
https://forum.lazarus.freepascal.org/index.php/topic,67003.msg514679.html#msg514679Thus if speed matters, I can use the procedural version. But nevermind, that is not the issue. What concerns me is that calls like
C1 := C1+C2;
add (C1,C2,C1);
break the "const" contract; C1 will be changed although declared as const. It works, the compiler does not care, but it gives me doubts. This is not clean.
So why don't I change "const" to "var":
procedure Add (var a,b: complex; out c: complex);
Fine. But - Ah !
Add(C1, Foo(), C2); //Error: can't take the address of constant expressions
Scratch my head. Oh yes. Understood. That does not work with functions as arguments.
Well, then let's pass by value instead. I suspect this may be a bit slower because the compiler now has to make copies of a and b, presumably using the slow REP MOVSL again. But let's try:
procedure Add (a,b: complex; out c: complex);
WTF, this is FACTOR 50 slower! Impressive, REP MOVSL hitting hard. Forget it!
Let's go back to the "const" version which is the only one that works. To prevent const parameters from being changed, we could check for identical addresses in the argument:
procedure Add (const a,b: complex; out c: complex);
begin
assert ((@a<>@c) and (@b<>@c);
(....);
end;
Nice, and less than 10% overhead for checking the assertion. That should be bulletproof.
BUT - Ah!- the assertion fails on "add (C1,C2,C1)" or "C1 := C1+C2". Yes, I see. Of course it does. But I need such calls.
What shall I do ? I went back to the original version, ignoring that the const parameters may in fact change, and hoping that the compiler will not stumble across it one day.