pure functions cannot be removed, actually: pure vs. total

Steven Schveighoffer schveiguy at yahoo.com
Thu Jun 7 22:23:09 UTC 2018


On 6/7/18 5:04 PM, Jonathan M Davis wrote:
> On Thursday, June 07, 2018 20:14:19 Johan Engelen via Digitalmars-d wrote:
>> On Tuesday, 5 June 2018 at 14:48:23 UTC, FeepingCreature wrote:
>>> I'm just posting to clear up the misunderstanding that a call
>>> to a pure function can be removed. Actually, even calls to
>>> strongly pure functions cannot always be removed. This is
>>> because there is one thing that a pure function can do that
>>> will change program behavior if it is removed, even if it does
>>> not change any global state whatsoever: it can simply never
>>> return.
>>>
>>> void foo() pure { while (true) { } }
>>
>> This is not a valid program, in my opinion. (Still only my
>> opinion, because I have not found it in the D spec, so needs
>> adding.)
>> A valid C++ program must make progress in each thread. C++ spec
>> states:
>> "The implementation may assume that any thread will eventually do
>> one of the following:
>> - terminate,
>> - make a call to a library I/O function,
>> - access or modify a volatile object, or
>> - perform a synchronization operation or an atomic operation."
> 
> I would be _very_ surprised if the D spec said anything like this simply
> because it doesn't do a lot of talking about conceptual stuff like this and
> talks a lot more about what the compiler actually does (or is supposed to
> do) - though that certainly doesn't mean that the spec shouldn't say
> something like that, and Walter has previously stated that anything that
> needs to be in the spec but isn't should be reported in bugzilla. So, if you
> have anything like this that you really think should be in the spec, please
> report it in bugzilla.
> 
> Regardless, I see zero value in supporting something like
> 
> void foo() pure { while(true) {} }

OK, so FeepingCreature has come up with an interesting case that's 
*similar* to this, and I think it makes things a little less cut and dry.

Let's say you have an infinite range that returns a single value. But 
instead of that value being stored in the range itself, it's an enum.

Maybe something like this:

struct Range
{
    enum empty = false;
    enum front = true;
    void popFront() pure nothrow {}
}

Now, let's say you ran find on it:

auto result = Range.init.find(false);

This *should* do an infinite loop. If it returned, it means that find 
has found where `false` is!

But let's dissect the call to `find`:

* It's a template, and everything that it calls, given this range is 
pure nothrow, so it is inferred pure nothrow.
* It accepts a type that has no mutable data.
* It returns a type that has no mutable data.
* The return type can be proven to not depend on the input.
* Therefore, it is a strong-pure function.

So logically, the compiler could assume if it thinks infinite loops are 
invalid, that it must return. Since it doesn't really need to call the 
function to see what it returns, it can just replace the code with:

auto result = Range.init;

And here, we have found `false` in an infinite range that only contains 
`true`.

So the danger of assuming no infinite loops is that some convoluted case 
like true.repeat.find(false) might incorrectly return something. Note 
that the repeat call wouldn't cause this problem, since it stores a 
mutable element inside it.

I still think that for void-returning functions we can simply assume to 
not do anything. Literally, the only thing it can do is return or 
infinite loop.

But I'm definitely not as sure as I was when I first responded to this 
thread.

-Steve


More information about the Digitalmars-d mailing list