November 13, 2021
On Saturday, 13 November 2021 at 14:32:15 UTC, Paul Backus wrote:
> On Saturday, 13 November 2021 at 13:11:05 UTC, Timon Gehr wrote:
>> [...]
>
> In a language where valid programs cannot distinguish between different pointers to the same value, or observe the side effects of memory allocation, it is natural to define `pure` such that it allows memory allocation.
>
> [...]

Many use the "compromise edition" to pure, I think it's ok. Pure in that sense is just same out for a given in, doesn't matter what the function does inside.
November 13, 2021

On Saturday, 13 November 2021 at 14:35:36 UTC, Imperatorn wrote:

>

On Saturday, 13 November 2021 at 14:32:15 UTC, Paul Backus wrote:

>

On Saturday, 13 November 2021 at 13:11:05 UTC, Timon Gehr wrote:

>

[...]

In a language where valid programs cannot distinguish between different pointers to the same value, or observe the side effects of memory allocation, it is natural to define pure such that it allows memory allocation.

[...]

Many use the "compromise edition" to pure, I think it's ok. Pure in that sense is just same out for a given in, doesn't matter what the function does inside.

pureMalloc does not return the same output for a given input.

November 13, 2021

On Saturday, 13 November 2021 at 14:32:15 UTC, Paul Backus wrote:

>

In a language where valid programs cannot distinguish between different pointers to the same value, or observe the side effects of memory allocation, it is natural to define pure such that it allows memory allocation.

D is not that kind of language.

I think it's supposed to be. Quoting the language spec:

>

Implementation Defined: An implementation may assume that a strongly pure function that returns a result without mutable indirections will have the same effect for all invocations with equivalent arguments. It is allowed to memoize the result of the function under the assumption that equivalent parameters always produce equivalent results. A strongly pure function may still have behavior inconsistent with memoization by e.g. using casts or by changing behavior depending on the address of its parameters. An implementation is currently not required to enforce validity of memoization in all cases. If a strongly pure function throws an Exception or an Error, the assumptions related to memoization do not carry to the thrown exception.

I think this means that while the language lets you to change what pure auto foo(immutable(int)[]) returns based on the address of the argument, the language is allowed to cache the result as if you could not do that. The spec only talks about immutable references, but still.

>

[1] Actually, the spec says that the compiler is allowed to assume referential transparency anyway, which turns this from an unfortunate limitation into a loaded foot-gun.

Ah, you already realized what I just said above. Yeah, it's an unfortunate trap but what do you do? Compiler optimisation is based on assumptions and assumptions usually necessiate pitfalls like this.

I think it would be a good idea to provide a compiler switch to disable pure assumptions for programs that want to give optimisations away for more reliability, but it shouldn't be a language rule.

Fortunately it's "only" implementation-defined thing - not fully undefined behaviour.

November 13, 2021

On Saturday, 13 November 2021 at 15:01:08 UTC, Dukc wrote:

> >

[1] Actually, the spec says that the compiler is allowed to assume referential transparency anyway, which turns this from an unfortunate limitation into a loaded foot-gun.

Ah, you already realized what I just said above. Yeah, it's an unfortunate trap but what do you do? Compiler optimisation is based on assumptions and assumptions usually necessiate pitfalls like this.

What you do is have the compiler enforce the preconditions necessary to make its assumptions hold. So, if the result of a pure function is not supposed to depend on the specific values of any pointers, pure functions should be forbidden from comparing pointers, casting them to integers, or performing any other operations that might introduce such a dependency.

Or, alternatively, if that's too much trouble, you recognize that the assumption underlying this optimization is invalid, and you remove it from the compiler and the language spec. (Which I expect is what will happen in practice, sooner or later.)

November 13, 2021

On Saturday, 13 November 2021 at 14:43:00 UTC, Paul Backus wrote:

>

On Saturday, 13 November 2021 at 14:35:36 UTC, Imperatorn wrote:

>

On Saturday, 13 November 2021 at 14:32:15 UTC, Paul Backus wrote:

>

On Saturday, 13 November 2021 at 13:11:05 UTC, Timon Gehr wrote:

>

[...]

In a language where valid programs cannot distinguish between different pointers to the same value, or observe the side effects of memory allocation, it is natural to define pure such that it allows memory allocation.

[...]

Many use the "compromise edition" to pure, I think it's ok. Pure in that sense is just same out for a given in, doesn't matter what the function does inside.

pureMalloc does not return the same output for a given input.

Yeah, these words are not precise enough.

November 13, 2021

On Saturday, 13 November 2021 at 16:57:47 UTC, Imperatorn wrote:

>

On Saturday, 13 November 2021 at 14:43:00 UTC, Paul Backus wrote:

>

On Saturday, 13 November 2021 at 14:35:36 UTC, Imperatorn wrote:

>

Many use the "compromise edition" to pure, I think it's ok. Pure in that sense is just same out for a given in, doesn't matter what the function does inside.

pureMalloc does not return the same output for a given input.

Yeah, these words are not precise enough.

To be fair, the language spec is also not very precise about this:

>

An implementation may assume that a strongly pure function that returns a result without mutable indirections will have the same effect for all invocations with equivalent arguments. It is allowed to memoize the result of the function under the assumption that equivalent parameters always produce equivalent results.

What does "equivalent" mean, here? The intent seems to be something like "equal, if you ignore differences in memory addresses," but the term is never actually defined.

November 13, 2021

On Saturday, 13 November 2021 at 15:16:43 UTC, Paul Backus wrote:

>

What you do is have the compiler enforce the preconditions necessary to make its assumptions hold. So, if the result of a pure function is not supposed to depend on the specific values of any pointers, pure functions should be forbidden from comparing pointers, casting them to integers, or performing any other operations that might introduce such a dependency.

That would undermine performance and render pure even more useless than it already is.

>

Or, alternatively, if that's too much trouble, you recognize that the assumption underlying this optimization is invalid, and you remove it from the compiler and the language spec. (Which I expect is what will happen in practice, sooner or later.)

Just remove the notion of normative "strongly pure", it is a big mistake to split a concept based on the input typing. Confusing two concepts like this in one construct is making the language more complex than keeping the concepts separate. It is ugly. Keep it clean, keep semantics of each concept simple.

November 13, 2021

On Saturday, 13 November 2021 at 18:06:37 UTC, Ola Fosheim Grøstad wrote:

>

On Saturday, 13 November 2021 at 15:16:43 UTC, Paul Backus wrote:

>

What you do is have the compiler enforce the preconditions necessary to make its assumptions hold. So, if the result of a pure function is not supposed to depend on the specific values of any pointers, pure functions should be forbidden from comparing pointers, casting them to integers, or performing any other operations that might introduce such a dependency.

That would undermine performance and render pure even more useless than it already is.

Not at all. He suggested having the compiler to catch the mistakes, not the runtime.

I'm all in for that, if we can find ways which don't break too much valid code. I'm not sure that is possible though.

>

Just remove the notion of normative "strongly pure", it is a big mistake to split a concept based on the input typing. Confusing two concepts like this in one construct is making the language more complex than keeping the concepts separate. It is ugly. Keep it clean, keep semantics of each concept simple.

I'm assuming you mean that the language would allow caching values even with arguments with mutable indirection, if the compiler can prove they are similar to an earlier call. I agree but that will do nothing to solve the issue (repeated calls to functions being wrongly skipped because accidently misusing pure). Rather the problem will be magnified.

Well, I almost agree. The problem is that free would break.

November 13, 2021

On Saturday, 13 November 2021 at 18:55:11 UTC, Dukc wrote:

>

Not at all. He suggested having the compiler to catch the mistakes, not the runtime.

He argued against using identity and relying fully on content, that is crazy expensive in common situations/algorithms.

This is not practical in a language where you expect to use library abstractions. As such, you end up not being able to use "pure" for anything that is non-trivial.

>

I'm assuming you mean that the language would allow caching values even with arguments with mutable indirection, if the compiler can prove they are similar to an earlier call.

If we want the programmer to use "proper purity" for caching values in libraries then it should be a separate construct that is enforcing norms on the program that are stronger than the regular "weakly pure". It should have a different syntax and very clear normative standards so people understand the difference. Meaning dealing rigorously with termination, nontrivial destructors, exceptions, allocations etc etc. If not able to deal with it rigorously: ban those constructs. Compiler type system should err on the side of correctness.

If you want the compiler to cache values, then it should just be based on static analysis of weakly pure. Compiler optimisation should err on the side of correctness.

But saying that "weakly pure" syntax with immutable input is providing a different set of semantics that somehow evolve over time on some sort of slippery slope is likely to lead up to a mess.

Just stick to the keyword pure meaning "no globals", and leave it at that. Want more? Introduce a new well defined concept.

November 14, 2021

On Saturday, 13 November 2021 at 21:26:46 UTC, Ola Fosheim Grøstad wrote:

>

On Saturday, 13 November 2021 at 18:55:11 UTC, Dukc wrote:

>

Not at all. He suggested having the compiler to catch the mistakes, not the runtime.

He argued against using identity and relying fully on content, that is crazy expensive in common situations/algorithms.

This is not practical in a language where you expect to use library abstractions. As such, you end up not being able to use "pure" for anything that is non-trivial.

In @safe code with only the language constructs, that is. You could still write sound @trusted pure library functions that can do those in efficient way.

> >

I'm assuming you mean that the language would allow caching values even with arguments with mutable indirection, if the compiler can prove they are similar to an earlier call.

Just stick to the keyword pure meaning "no globals", and leave it at that. Want more? Introduce a new well defined concept.

My mistake, you were proposing the exact opposite of what I thought. That's the surest way to go but I hope it won't be necessary. It'd leave pure a bit lame.