Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2018-08

Pure function development discussion

4 messages · 2 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

J. Gareth Moreton

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

Reply by email

J. Gareth Moreton

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

Reply by email

J. Gareth Moreton

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"
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

Reply by email

Dmitry Boyarintsev
to J. Gareth Moreton: Hi everyone.  The development of pure function support is going well, along with…
PM J. Gareth Moreton wrote 6 lines
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

Reply by email