digitalmars.D - Pure Factory Functions =?UTF-8?B?8J+SlA==?= `inout`
- Quirin Schroll (39/39) Feb 09 2023 In [Pure Factory
- Steven Schveighoffer (14/58) Feb 09 2023 a `pure` function which takes an `inout` reference and returns a
- Kagamin (5/9) Feb 10 2023 That's a weakly pure function, so its return value can convert
- Steven Schveighoffer (15/23) Feb 10 2023 It can be weakly pure. The docs are wrong.
- ag0aep6g (7/20) Feb 10 2023 That function "has no parameters with mutable indirections". So
- Steven Schveighoffer (18/41) Feb 10 2023 I thought strong purity had to do with call elision/memoization.
- ag0aep6g (21/40) Feb 10 2023 In my experience, David Nadlinger's "Purity in D"[1] is a better
- ag0aep6g (8/11) Feb 10 2023 Though I just realized that that can be interpreted two ways:
- Quirin Schroll (25/45) Feb 21 2023 Uniqueness analysis depends on the return type as much as on
- ag0aep6g (7/13) Feb 21 2023 I was only commenting on weakly/strongly pure. As you say, the return
- Quirin Schroll (22/35) Feb 21 2023 No, that is not true. The following `foo` is weakly pure, it
- ag0aep6g (20/36) Feb 21 2023 That right there is nothing but a safety hole. D allows casting from
- ag0aep6g (4/23) Feb 21 2023 I did some digging and found `-preview=fixImmutableConv`. With that, the...
- Quirin Schroll (22/35) Feb 21 2023 No, that is not true. The following `foo` is weakly pure, it
- Kagamin (19/20) Feb 10 2023 Looks like the check is deeper.
- ag0aep6g (5/21) Feb 10 2023 The spec currently says that the result of a pure factory
In [Pure Factory Functions](https://dlang.org/spec/function.html#pure-factory-functions), it says what conversions are enabled for the result of a strongly pure function because the result is unique. For one of my last PRs, I went through all possible combination of qualifiers and all pairs of these types. While I found no problematic conversion that was allowed, I did find conversions that should be allowed, but DMD rejects them. All of those involve `inout`; in particular, whatever of _mutable,_ `const`, and `immutable` you replace `inout` on both sides with (if at both sides), the conversion is allowed for a pure factory function, but not for `inout`. As far as I understand `inout`, this makes no sense and they should be allowed by D’s type system. Can someone take a look at the list and tell me if I’m mistaken with some (or all) of them? | “From” Type | “To” Type | |--------------------------|--------------------------| | _unshared_ `inout const` | `shared inout const` | | _unshared_ `inout` | `shared inout` | | _unshared_ `inout` | `shared inout const` | | _unshared_ `inout` | _mutable unshared_ | | _unshared_ `inout` | _mutable_ `shared` | | _unshared_ `const` | _unshared_ `inout const` | | _unshared_ `const` | `shared inout const` | | `shared inout const` | _unshared_ `inout const` | | `shared inout` | _unshared_ `inout` | | `shared inout` | _unshared_ `inout const` | | `shared inout` | _mutable unshared_ | | `shared inout` | _mutable_ `shared` | | `shared const` | _unshared_ `inout const` | | `shared const` | `shared inout const` | | _mutable unshared_ | _unshared_ `inout` | | _mutable unshared_ | _unshared_ `inout const` | | _mutable unshared_ | `shared inout` | | _mutable unshared_ | `shared inout const` | | _mutable_ `shared` | _unshared_ `inout` | | _mutable_ `shared` | _unshared_ `inout const` | | _mutable_ `shared` | `shared inout` | | _mutable_ `shared` | `shared inout const` | [Here](https://wandbox.org/permlink/DHdKvSrfXO512Kxh) is the code, for those interested.
Feb 09 2023
On 2/9/23 12:41 PM, Quirin Schroll wrote:In [Pure Factory Functions](https://dlang.org/spec/function.html#pure-factory-functions), it says what conversions are enabled for the result of a strongly pure function because the result is unique. For one of my last PRs, I went through all possible combination of qualifiers and all pairs of these types. While I found no problematic conversion that was allowed, I did find conversions that should be allowed, but DMD rejects them. All of those involve `inout`; in particular, whatever of _mutable,_ `const`, and `immutable` you replace `inout` on both sides with (if at both sides), the conversion is allowed for a pure factory function, but not for `inout`. As far as I understand `inout`, this makes no sense and they should be allowed by D’s type system. Can someone take a look at the list and tell me if I’m mistaken with some (or all) of them? | “From” Type | “To” Type | |--------------------------|--------------------------| | _unshared_ `inout const` | `shared inout const` | | _unshared_ `inout` | `shared inout` | | _unshared_ `inout` | `shared inout const` | | _unshared_ `inout` | _mutable unshared_ | | _unshared_ `inout` | _mutable_ `shared` | | _unshared_ `const` | _unshared_ `inout const` | | _unshared_ `const` | `shared inout const` | | `shared inout const` | _unshared_ `inout const` | | `shared inout` | _unshared_ `inout` | | `shared inout` | _unshared_ `inout const` | | `shared inout` | _mutable unshared_ | | `shared inout` | _mutable_ `shared` | | `shared const` | _unshared_ `inout const` | | `shared const` | `shared inout const` | | _mutable unshared_ | _unshared_ `inout` | | _mutable unshared_ | _unshared_ `inout const` | | _mutable unshared_ | `shared inout` | | _mutable unshared_ | `shared inout const` | | _mutable_ `shared` | _unshared_ `inout` | | _mutable_ `shared` | _unshared_ `inout const` | | _mutable_ `shared` | `shared inout` | | _mutable_ `shared` | `shared inout const` | [Here](https://wandbox.org/permlink/DHdKvSrfXO512Kxh) is the code, for those interested.a `pure` function which takes an `inout` reference and returns a `mutable` reference should always be implicitly convertible to anything you want. It's no different from `const` in this regard. Now, `inout` doesn't implicitly convert to/from `shared`. So throwing `shared` into the mix is likely to cancel some otherwise-correct conversions, but that's on the *argument* conversion, not the *return* conversion. The return conversion should always be valid. The idea is that the return value must be unique. I had a hard time following your code. Instead of using boilerplate generation, generate the boilerplate, run it, and then generate a complete file with the boilerplate that shows the conversions that don't work. -Steve
Feb 09 2023
On Thursday, 9 February 2023 at 18:47:17 UTC, Steven Schveighoffer wrote:a `pure` function which takes an `inout` reference and returns a `mutable` reference should always be implicitly convertible to anything you want. It's no different from `const` in this regard.That's a weakly pure function, so its return value can convert only to const. Maybe it can convert to inout too, but the language doesn't recognize this pattern.
Feb 10 2023
On 2/10/23 7:53 AM, Kagamin wrote:On Thursday, 9 February 2023 at 18:47:17 UTC, Steven Schveighoffer wrote:It can be weakly pure. The docs are wrong. i.e. this works: ```d pure int *foo(const int *p) { return new int(5); } void main() { int x; immutable int * bar = foo(&x); } ``` -Stevea `pure` function which takes an `inout` reference and returns a `mutable` reference should always be implicitly convertible to anything you want. It's no different from `const` in this regard.That's a weakly pure function, so its return value can convert only to const. Maybe it can convert to inout too, but the language doesn't recognize this pattern.
Feb 10 2023
On Friday, 10 February 2023 at 14:00:20 UTC, Steven Schveighoffer wrote:It can be weakly pure. The docs are wrong. i.e. this works: ```d pure int *foo(const int *p) { return new int(5); } void main() { int x; immutable int * bar = foo(&x); } ```That function "has no parameters with mutable indirections". So it's strongly pure, not weakly pure. I'd guess that `inout` counts like `const` with regards to weak/strong purity. So a `pure` function with `inout` parameters is also strongly pure.
Feb 10 2023
On 2/10/23 9:37 AM, ag0aep6g wrote:On Friday, 10 February 2023 at 14:00:20 UTC, Steven Schveighoffer wrote:I thought strong purity had to do with call elision/memoization. Clearly, calling with a const pointer can't be elided or memoized if passed a mutable. So the spec is consistent I guess, I just misunderstood what constituted "strong" purity. However, the Optimization section does state: 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." I feel like this is not a sound assumption if the parameter is const, and the argument is mutable. Of course, if the compiler can prove that the variable doesn't change elsewhere, then it's OK to memoize. But just assuming because it's strong pure is not correct. Of course, it depends on the definition of "equivalent arguments". -SteveIt can be weakly pure. The docs are wrong. i.e. this works: ```d pure int *foo(const int *p) { return new int(5); } void main() { int x; immutable int * bar = foo(&x); } ```That function "has no parameters with mutable indirections". So it's strongly pure, not weakly pure. I'd guess that `inout` counts like `const` with regards to weak/strong purity. So a `pure` function with `inout` parameters is also strongly pure.
Feb 10 2023
On Friday, 10 February 2023 at 18:01:56 UTC, Steven Schveighoffer wrote:I thought strong purity had to do with call elision/memoization. Clearly, calling with a const pointer can't be elided or memoized if passed a mutable. So the spec is consistent I guess, I just misunderstood what constituted "strong" purity. However, the Optimization section does state: 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." I feel like this is not a sound assumption if the parameter is const, and the argument is mutable. Of course, if the compiler can prove that the variable doesn't change elsewhere, then it's OK to memoize. But just assuming because it's strong pure is not correct. Of course, it depends on the definition of "equivalent arguments".In my experience, David Nadlinger's "Purity in D"[1] is a better source of truth than DMD or the spec when it comes to `pure`. As far as I can tell, the spec and "Purity in D" use the same definitions for "weakly pure" and "strongly pure". David also describes "pure factory functions" (without calling them that) as "not [taking] any arguments with mutable indirections". So we got that right, too. But on memoization, David says: "When coming across a pure function with immutable parameters, only the identity of the arguments has to be checked in order to be able to optimize several calls down to one [...]. On the other hand, if an argument type contains indirections and is only const, somebody else could modify the data between two calls, requiring »deep« comparisons that might not be feasible for large data structures in the runtime case, or extensive data flow analysis in a compiler." Looks like the spec misses the requirement of having only immutable parameters. [1] https://klickverbot.at/blog/2012/05/purity-in-d/
Feb 10 2023
On Friday, 10 February 2023 at 19:33:26 UTC, ag0aep6g wrote:David also describes "pure factory functions" (without calling them that) as "not [taking] any arguments with mutable indirections".Though I just realized that that can be interpreted two ways: 1. The function does not have any *parameters* with mutable indirections. I.e., `const` is ok. This is what DMD does. 2. The function cannot be called with mutable indirections. I.e., `const` is not ok. I think the first interpretation is the intended one, but I can't say I'm sure.
Feb 10 2023
On Friday, 10 February 2023 at 14:37:35 UTC, ag0aep6g wrote:On Friday, 10 February 2023 at 14:00:20 UTC, Steven Schveighoffer wrote:Uniqueness analysis depends on the return type as much as on parameter types. In Steve’s example, `foo` does have parameters with mutable indirections (as I interpret that phrase – it’s honestly not precise wording) because `const` allows binding mutable values. The reason Steve’s example compiles is that by thorough investigation of parameter and return types, the compiler comes to the conclusion that there is no way `p`, which is a `const(int)*` makes it into the return value, and *therefore* the result must be uniquely created and thus may be cast to `immutable`. If you change the return type of `foo` to `const(int)*` (cf. `goo` in the example below), the parameter’s value could make it into the result and because `p` may be bound to mutable, the result cannot be cast to `immutable` implicitly: ```d pure safe int * foo(const(int)* p) { return new int(1); } pure safe const(int)* goo(const(int)* p) { return new int(1); } void main() safe { auto p = new int; immutable a = foo(p); // ok immutable b = goo(p); // error } ```It can be weakly pure. The docs are wrong. i.e. this works: ```d pure int *foo(const int *p) { return new int(5); } void main() { int x; immutable int * bar = foo(&x); } ```That function "has no parameters with mutable indirections". So it's strongly pure, not weakly pure.
Feb 21 2023
On 21.02.23 11:46, Quirin Schroll wrote:On Friday, 10 February 2023 at 14:37:35 UTC, ag0aep6g wrote:[...]I was only commenting on weakly/strongly pure. As you say, the return type matters for pure factory functions. But it doesn't matter for weakly/strongly pure. All pure factory functions are strongly pure. Not all strongly pure functions are pure factory functions.That function "has no parameters with mutable indirections". So it's strongly pure, not weakly pure.Uniqueness analysis depends on the return type as much as on parameter types.
Feb 21 2023
On Tuesday, 21 February 2023 at 13:13:27 UTC, ag0aep6g wrote:On 21.02.23 11:46, Quirin Schroll wrote:No, that is not true. The following `foo` is weakly pure, it mutates its input. But because the input cannot end up in the result (proven via their types, not the implementation), the result is unique, and can be converted to immutable. ```d safe: int* foo(double[] xs) pure { if (xs.length >= 2) xs[0] = xs[$ - 1]; return new int; } void main() { double[] xs = []; immutable a = foo(xs); } ``` The whole section of pure factory functions should be revised (it could moved to a textbook like Ali’s) because it wants to talk about unique results, but it talks about a special case of it.On Friday, 10 February 2023 at 14:37:35 UTC, ag0aep6g wrote:[...]I was only commenting on weakly/strongly pure. As you say, the return type matters for pure factory functions. But it doesn't matter for weakly/strongly pure. All pure factory functions are strongly pure. Not all strongly pure functions are pure factory functions.That function "has no parameters with mutable indirections". So it's strongly pure, not weakly pure.Uniqueness analysis depends on the return type as much as on parameter types.
Feb 21 2023
On 21.02.23 14:58, Quirin Schroll wrote:```d safe: int* foo(double[] xs) pure { if (xs.length >= 2) xs[0] = xs[$ - 1]; return new int; } void main() { double[] xs = []; immutable a = foo(xs); } ```That right there is nothing but a safety hole. D allows casting from `double[]` to `int[]`, so the compiler cannot assume uniqueness in that case. More explosive test case: ---- safe: int[] foo(ubyte[] bytes) safe pure { return cast(int[]) bytes; /* the language very much allows this */ } void main() { ubyte[] bytes = [42, 0, 0, 0]; immutable ints = foo(bytes); assert(ints[0] == 42); /* passes */ bytes[0] = 13; assert(ints[0] == 42); /* fails; immutable int changed value */ } ----
Feb 21 2023
On 21.02.23 15:31, ag0aep6g wrote:On 21.02.23 14:58, Quirin Schroll wrote:I did some digging and found `-preview=fixImmutableConv`. With that, the code fails compilation as it should. So this is a known issue and it's already in the process of getting fixed.```d safe: int* foo(double[] xs) pure { if (xs.length >= 2) xs[0] = xs[$ - 1]; return new int; } void main() { double[] xs = []; immutable a = foo(xs); } ```That right there is nothing but a safety hole.
Feb 21 2023
On Tuesday, 21 February 2023 at 13:13:27 UTC, ag0aep6g wrote:On 21.02.23 11:46, Quirin Schroll wrote:No, that is not true. The following `foo` is weakly pure, it mutates its input. But because the input cannot end up in the result (proven via their types, not the implementation), the result is unique, and can be converted to immutable. ```d safe: int* foo(double[] xs) pure { if (xs.length >= 2) xs[0] = xs[$ - 1]; return new int; } void main() { double[] xs = []; immutable a = foo(xs); } ``` The whole section of pure factory functions should be revised (it could moved to a textbook like Ali’s) because it wants to talk about unique results, but it talks about a special case of it.On Friday, 10 February 2023 at 14:37:35 UTC, ag0aep6g wrote:[...]I was only commenting on weakly/strongly pure. As you say, the return type matters for pure factory functions. But it doesn't matter for weakly/strongly pure. All pure factory functions are strongly pure. Not all strongly pure functions are pure factory functions.That function "has no parameters with mutable indirections". So it's strongly pure, not weakly pure.Uniqueness analysis depends on the return type as much as on parameter types.
Feb 21 2023
On Friday, 10 February 2023 at 14:00:20 UTC, Steven Schveighoffer wrote:It can be weakly pure. The docs are wrong.Looks like the check is deeper. This doesn't work: ```d struct A { const int[] a; } pure A a(const int[] b) { return A(b); } void e() { int[] b; immutable A c=a(b); } ```
Feb 10 2023
On Friday, 10 February 2023 at 15:23:04 UTC, Kagamin wrote:This doesn't work: ```d struct A { const int[] a; } pure A a(const int[] b) { return A(b); } void e() { int[] b; immutable A c=a(b); } ```The spec currently says that the result of a pure factory function "has mutable indirections". It should probably say that the result "has *only* mutable indirections". Then it would be clear that `a` is not a pure factory function.
Feb 10 2023