www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - What can _not_ be marked pure?

reply Guillaume Piolat <contact gam3sfrommars.fr> writes:
If I understand purity correctly 
(http://klickverbot.at/blog/2012/05/purity-in-d/), every function 
out there can be marked pure as long as it doesn't modify 
globals, shared variables or do I/O?

It seems more function can be marked pure that I previously 
thought.
Mar 09 2016
next sibling parent reply Guillaume Piolat <contact gam3sfrommars.fr> writes:
On Wednesday, 9 March 2016 at 09:56:05 UTC, Guillaume Piolat 
wrote:
 If I understand purity correctly 
 (http://klickverbot.at/blog/2012/05/purity-in-d/), every 
 function out there can be marked pure as long as it doesn't 
 modify globals, shared variables or do I/O?

 It seems more function can be marked pure that I previously 
 thought.
Another question that ensues is: will the compiler prevent incorrect use of pure, so that it's safe to spam it in your code?
Mar 09 2016
parent ag0aep6g <anonymous example.com> writes:
On 09.03.2016 10:57, Guillaume Piolat wrote:
 Another question that ensues is: will the compiler prevent incorrect use
 of pure, so that it's safe to spam it in your code?
The compiler should catch wrong usage of `pure`, yes. Function declarations without implementation are an exception, of course. Same with `nothrow`, ` safe`, and ` nogc`. The compiler can't check ` trusted` for correctness, obviously.
Mar 09 2016
prev sibling next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 09.03.2016 10:56, Guillaume Piolat wrote:
 If I understand purity correctly
 (http://klickverbot.at/blog/2012/05/purity-in-d/), every function out
 there can be marked pure as long as it doesn't modify globals, shared
 variables or do I/O?
Pure functions also can't *read* mutable globals. But then that's it, I think.
Mar 09 2016
parent reply Guillaume Piolat <contact gam3sfrommars.fr> writes:
On Wednesday, 9 March 2016 at 10:08:33 UTC, ag0aep6g wrote:
 On 09.03.2016 10:56, Guillaume Piolat wrote:
 If I understand purity correctly
 (http://klickverbot.at/blog/2012/05/purity-in-d/), every 
 function out
 there can be marked pure as long as it doesn't modify globals, 
 shared
 variables or do I/O?
Pure functions also can't *read* mutable globals. But then that's it, I think.
Thanks. Now I'm excited to spam it over my codebase and see the kind of speedup one can achieve.
Mar 09 2016
parent Daniel Kozak via Digitalmars-d-learn <digitalmars-d-learn puremagic.com> writes:
Dne 9.3.2016 v 11:26 Guillaume Piolat via Digitalmars-d-learn napsal(a):
 On Wednesday, 9 March 2016 at 10:08:33 UTC, ag0aep6g wrote:
 On 09.03.2016 10:56, Guillaume Piolat wrote:
 If I understand purity correctly
 (http://klickverbot.at/blog/2012/05/purity-in-d/), every function out
 there can be marked pure as long as it doesn't modify globals, shared
 variables or do I/O?
Pure functions also can't *read* mutable globals. But then that's it, I think.
Thanks. Now I'm excited to spam it over my codebase and see the kind of speedup one can achieve.
I am not sure, but my guess is there will be no speed up :-).
Mar 09 2016
prev sibling next sibling parent reply Jonathan M Davis via Digitalmars-d-learn writes:
On Wednesday, March 09, 2016 09:56:05 Guillaume Piolat via Digitalmars-d-learn 
wrote:
 If I understand purity correctly
 (http://klickverbot.at/blog/2012/05/purity-in-d/), every function
 out there can be marked pure as long as it doesn't modify
 globals, shared variables or do I/O?

 It seems more function can be marked pure that I previously
 thought.
Basically, a pure function cannot call a function which is not pure, and it can't access any global or static variables which are mutable (or const if the type has indirections - the variable has to be completely unchangeable to be accessible). And that's it. shared has nothing to do with it one way or the other, and I/O isn't explicitly forbidden, but not being able to access mutable globals or non-pure functions naturally kills I/O. The exception is that inside of debug{} blocks, purity is ignored, so you can do I/O there, but it's intended purely for debugging purposes. So, in general, you can slap pure on most anything, though it will rarely buy you anything in terms of performance. What it primarily buys you is the knowledge that you're not messing with global variables unless the function's arguments give you access to them. The second major gain is that in many cases, a pure function's return type can have its constancy inferred, making it possible to allocate a mutable object inside of a pure function, do whatever needs to be done to it to set it up, and then have it implicitly converted to immutable when it's returned (for that to work though, the compiler has to be able to guarantee that the return value didn't come from any of the function arguments). The main place where performance gains could take place would be in cases where a pure function is called multiple times in the same expression with the same arguments, and the compiler is able to determine that it's impossible for those arguments to have been mutated between calls, allowing the compiler to just make the call once and reuse the result. AFAIK, that currently requires that the function parameters be immutable or implicitly convertible to immutable, though in theory, it should be possible for const parameters when the arguments are immutable. Regardless, it's occasionally useful but not very often. Having lots of pure functions makes it easier to have useful functions with immutable arguments, whereas when pure functions always required that the parameters be immutable (or implicitly convertible to immutable), the really couldn't do much. But the result is that most pure functions can't really be optimized base on their purity. The two places that you need to be careful with pure though are virtual functions and templated functions. If a base class function is pure, then the derived class overrides also have to be pure, whereas if the base class function isn't pure, then the derived class function can't normally be pure (it _might_ be able to be pure if it doesn't call the base class function). So, when dealing with virtual functions, you have to be careful about whether you want to avoid restricting virtual functions from being used in pure code (by not marking the base class function pure) or whether you want to restrict what derived class functions can do (by marking the base class function pure). The reason to avoid pure with templated functions is that whether a templated function can really be pure or not usually depends on its arguments. If the types it's instantiated with use pure functions, then it can be pure, whereas if they don't, then it can't be. So, you'd have a similar problem to that of virtual functions except that pure, nothrow, and safe are inferred for template functions, so if the function doesn't do anything which makes it so that it can't be pure, the compiler should infer it to be pure without you needing to mark it as pure. In general though, you should use pure wherever possible. - Jonathan M Davis
Mar 09 2016
parent Guillaume Piolat <contact gam3sfrommars.fr> writes:
On Wednesday, 9 March 2016 at 13:12:18 UTC, Jonathan M Davis 
wrote:
 In general though, you should use pure wherever possible.

 - Jonathan M Davis
Thanks for the detailed answer and gotchas. It thought compilers would use pure in alias analysis to ensure everything did not mutate during a pure function call. This topic is much more involved than it first seemed! Reminds me of the quote: "I have yet to see any problem, however complicated, which, when you looked at it in the right way, did not become still more complicated."
Mar 09 2016
prev sibling parent "H. S. Teoh via Digitalmars-d-learn" <digitalmars-d-learn puremagic.com> writes:
On Wed, Mar 09, 2016 at 05:12:18AM -0800, Jonathan M Davis via
Digitalmars-d-learn wrote:
[...]
 So, in general, you can slap pure on most anything, though it will
 rarely buy you anything in terms of performance.
IMO, this is an area where the compiler could be improved to take better advantage of pure annotations. The currently-implemented optimizations based on pure, while nice, are also relatively rare, and don't seem to outweigh the cost of having to annotate large numbers of functions by hand. I think one area where pure could potentially have a large gain is in loop optimization, where pure annotations may help in increasing the amount of code that can be hoisted out of the loop. Contrived example: long veryExpensiveFunction(T)(T arg) pure { ... } long func(long x) { long accum; foreach (i; 0 .. 1_000_000) { accum += i*veryExpensiveFunction(x); } return accum; } If veryExpensiveFunction() is not pure, we cannot hoist it out of the loop, since it may change the program's semantics. However, if it's pure, we can potentially have a large performance gain by hoisting it outside the loop. (One may argue that a good programmer would already do this manually, of course, but the thing about these kinds of loop optimizations is that they often exhibit domino-effects, where the above code snippet isn't what the programmer actually wrote, but the result of a previous optimization on the original code, say an inlining, or some such. Having the compiler detect this case may then lead to more optimization opportunities downstream. While dmd is, IME, rather poor at following these optimization domino chains, gdc's optimizer has a lot of aggressive loop optimizations, and may be able to do much better than it can now if the frontend can feed this kind of information about purity to it.) [...]
 The reason to avoid pure with templated functions is that whether a
 templated function can really be pure or not usually depends on its
 arguments.
I think a useful pattern that has emerged in Phobos code is that all template functions, in general, should have no attributes -- it should be left to the compiler to infer them. To ensure that the code itself doesn't break purity (etc.), use a pure (etc.) unittest: auto func(T)(T args) // N.B.: no attributes { ... } pure unittest { // We know that int will not introduce impurity to // func(), so this unittest will break if func itself // contains impure code aside from its template // arguments. func(1); } This way, we ensure that the compiler will infer func as pure whenever it can, yet we don't restrict T to be also pure (in the case where T is impure, the compiler will correctly infer func!T as impure). Attribute inference is the way of the future. T -- Let's eat some disquits while we format the biskettes.
Mar 09 2016