Jump to page: 1 2
Thread overview
Pure Factory Functions 💔 `inout`
Feb 09, 2023
Quirin Schroll
Feb 10, 2023
Kagamin
Feb 10, 2023
ag0aep6g
Feb 10, 2023
ag0aep6g
Feb 10, 2023
ag0aep6g
Feb 21, 2023
Quirin Schroll
Feb 21, 2023
ag0aep6g
Feb 21, 2023
Quirin Schroll
Feb 21, 2023
ag0aep6g
Feb 21, 2023
ag0aep6g
Feb 21, 2023
Quirin Schroll
Feb 10, 2023
Kagamin
Feb 10, 2023
ag0aep6g
February 09, 2023

In 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 is the code, for those interested.

February 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
February 10, 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.
February 10, 2023

On 2/10/23 7:53 AM, Kagamin wrote:

>

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.

It can be weakly pure. The docs are wrong.

i.e. this works:

pure int *foo(const int *p)
{
   return new int(5);
}

void main()
{
   int x;
   immutable int * bar = foo(&x);
}

-Steve

February 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:

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.

February 10, 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:

struct A
{
	const int[] a;
}
pure A a(const int[] b)
{
	return A(b);
}
void e()
{
	int[] b;
	immutable A c=a(b);
}
February 10, 2023

On Friday, 10 February 2023 at 15:23:04 UTC, Kagamin wrote:

>

This doesn't work:

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.

February 10, 2023

On 2/10/23 9:37 AM, ag0aep6g wrote:

>

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:

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.

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

-Steve

February 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/

February 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.

« First   ‹ Prev
1 2