Discussion:
Pure function development discussion
(too old to reply)
J. Gareth Moreton
2018-08-11 23:48:46 UTC
Permalink
Hi everyone.  The development of pure function support is going well,
along with catching error cases.  Thanks to some discussion from Sven, the
compiler will now detect if you reference a function before defining it as
"pure", so cases where it's defined as "forward" are trapped as errors so
as to not cause problematic behaviour.  I'm still a little unsure of it in
places, but so far, I have been running the attached file through the
compiler.

PureTestUnit.pas(5,10) Hint: Function "PureMin" is eligible for the "pure"
directive
PureTestUnit.pas(33,15) Warning: Procedure is not eligible to be pure
(impure function call)
PureTestUnit.pas(37,1) Error: Function declared pure after it has already
been referenced
PureTestUnit.pas(44,4) Fatal: There were 1 errors compiling module,
stopping

In this situation, the parsing of the "Binomial" function, which has been
declared to be pure, raises a warning because it calls the Factorial
function, which hasn't been declared as such in the interface, but later on
in the implementation, and then the error is because the implementation of
Factorial appears after it's already been used.  If you rearrange the code
so Factorial appears before Binomial, the error and warning vanish. 
Currently that feels a bit clumsy to me.

One question I've yet to find an answer for is when you have something
like the following:

interface

function ln(x: Double): Double; pure;

const
  LN2 = ln(2);

implementation

function HalfLife(decay: Double): Double;
begin
  Result := LN2 / decay;
end;

function ln(x: Double): Double;
begin  { Code to calculate natural lograithm }
end;

How and when is the constant defined? Should such constructs be disallowed
and constants only allowed to be declared in other units that use the unit
that contains the pure functions in its interface section? What would be
the ideal and cleanest behaviour?

Gareth aka. Kit
J. Gareth Moreton
2018-08-12 04:19:56 UTC
Permalink
I'm still figuring bits and pieces out, but I've managed to change the
checks so the error that I listed in the last e-mail only appears for
forward-declared functions, not interface + implementation, since I believe
everything gets fully defined by the time the first pass comes along.  I'm
still working out the nuances, and I just hope it all works and isn't a
clumsy feature to use.

As for the constants, I figure the best approach is to return an error if
one attempts to use them before they are fully defined.  I think there's
infrastructure in the compiler to allow that - we'll see.

Gareth aka. Kit
Dmitry Boyarintsev
2018-08-12 14:28:20 UTC
Permalink
Post by J. Gareth Moreton
How and when is the constant defined? Should such constructs be disallowed
and constants only allowed to be declared in other units that use the unit
that contains the pure functions in its interface section? What would be
the ideal and cleanest behaviour?
Why don't you limit the purity only for expressions explicitly defined at
"const" section?
If the function call is found at any place other than "const" section, the
function would be executed as a regular run-time function.
Pretty clean.

thanks,
Dmitry
J. Gareth Moreton
2018-08-12 14:04:38 UTC
Permalink
Limiting pure functions to the definitions of constants severely limits
their usefulness, and programmers may just ignore them completely and
calculate their results by hand where needed (e.g. replacing ln(2) with
0.69 etc.).  They're designed to replace entire function calls with
pre-calculated results at compile-time, thereby providing a massive speed
and size saving, and in places where you might least expect, depending on
how functions are expanded recursively.  For example, say you have a
binary search function that's declared "inline", and you pass in a constant
array of a static size.  The function call is expanded and the parameter
propagated.  You may then call a function that calculates the base-2
logarithm of the array's size, rounded down, as this is equal to the
maximum number of iterations required in the search loop.  Since the size
is a constant (since the parameter is propagated unchanged), so too will
its logarithm, so the call is replaced with the pre-calculated result. 
Since this logarithm is relatively small (e.g. a list of size 200 will
return a value of 7), this may cause the loop to be unrolled, providing yet
more speed boosts by removing the need for branch prediction.  That's in a
very ideal world, of course, where all the optimisations work in unison.
Also, even with your suggestion, there are some cases where the programmer
can be deliberately malicious, such as defining a constant to equal the
result of a pure function, and then referencing the constant inside the
function itself.  I do have an idea though: I may treat them as a kind of
inline expression that references are replaced with (e.g. with "const LN2 =
ln(2)", replacing references to LN2 with the actual ln(2) call) until it's
been successfully evaluated, after which the constant can just be replaced
with its value.  If a function turns out to be impure during evaluation of
the constant, or appears to have an infinite loop (the compiler will fail
the evaluation if it reaches an upper limit on node count or stack depth),
then an error is thrown and the constant marked invalid, hence the compiler
will fail cleanly.

Granted, I may put constant evaluation on the side for the moment and
actually get the compile-time evaluation to work first.  I'm just trying
to think of every possible eventuality and cleanly cover the all.

Gareth aka. Kit

On Sun 12/08/18 15:28 , "Dmitry Boyarintsev" ***@gmail.com
sent:
On Sat, Aug 11, 2018 at 8:50 PM J. Gareth Moreton wrote:
How and when is the constant defined? Should such constructs be disallowed
and constants only allowed to be declared in other units that use the unit
that contains the pure functions in its interface section? What would be
the ideal and cleanest behaviour?

Why don't you limit the purity only for expressions explicitly defined at
"const" section?If the function call is found at any place other than
"const" section, the function would be executed as a regular run-time
function.Pretty clean.

thanks,Dmitry

Loading...