December 21, 2016
On Wednesday, 21 December 2016 at 02:27:02 UTC, Andrei Alexandrescu wrote:
> If it's elidable, it's as good as a bug in the program. Must be either a compile-time error or a special case. -- Andrei

I can't see it ending well to make it this kind of special case.  For example, one day someone will take one of these not-really-pure-by-any-definition-but-labelled-pure-for-some-reason-and-treated-as-a-special-case functions and make it return, I don't know, an int, and then be surprised that the compiler now elides the function call.  If users rely on this special case behaviour, eventually someone will need a way to make a "pure"-with-side-effects function return values without being treated like a pure function.

On the other hand, making it a compilation error might get in the way of generic programming, so that's worth considering.
December 21, 2016
On 21.12.2016 01:58, Andrei Alexandrescu wrote:
> On 12/20/16 7:40 PM, Timon Gehr wrote:
>> On 20.12.2016 23:49, Andrei Alexandrescu wrote:
>>> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
>>
>> Good, except:
>>
>> "$(P `pure` functions returning `void` will be always called even if it
>> is strongly `pure`. The implementation must assume the function does
>> something outside the confines of the type system and is therefore not
>> allowed to elide the call, even if it appears to have no possible
>> effect.)"
>>
>> I think this makes no sense. What is the idea behind this paragraph?
>
> A function that traces execution via a debug statement, for example. --
> Andrei

IMNSHO, that shouldn't restrict a non-debug build, and it should not be thought of as being outside the type system's confines. Also:

- 'void' should not be a special case: either all pure functions can be optimized, or none of them. (a void-returning pure function can be called in a non-void-returning pure function.)

- pure functions cannot be elided without considering their bodies in in any case, as they can terminate the program by throwing an error:

immutable(void) fail()pure nothrow @safe{
    throw new Error("boom!");
}

or cause the program to fail to terminate:

immutable(void) loop()pure nothrow @safe{
    for(;;){}
}
December 21, 2016
On 12/21/2016 05:08 AM, Timon Gehr wrote:
> On 21.12.2016 01:58, Andrei Alexandrescu wrote:
>> On 12/20/16 7:40 PM, Timon Gehr wrote:
>>> On 20.12.2016 23:49, Andrei Alexandrescu wrote:
>>>> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
>>>
>>> Good, except:
>>>
>>> "$(P `pure` functions returning `void` will be always called even if it
>>> is strongly `pure`. The implementation must assume the function does
>>> something outside the confines of the type system and is therefore not
>>> allowed to elide the call, even if it appears to have no possible
>>> effect.)"
>>>
>>> I think this makes no sense. What is the idea behind this paragraph?
>>
>> A function that traces execution via a debug statement, for example. --
>> Andrei
>
> IMNSHO, that shouldn't restrict a non-debug build, and it should not be
> thought of as being outside the type system's confines. Also:
>
> - 'void' should not be a special case: either all pure functions can be
> optimized, or none of them. (a void-returning pure function can be
> called in a non-void-returning pure function.)
>
> - pure functions cannot be elided without considering their bodies in in
> any case, as they can terminate the program by throwing an error:
>
> immutable(void) fail()pure nothrow @safe{
>     throw new Error("boom!");
> }
>
> or cause the program to fail to terminate:
>
> immutable(void) loop()pure nothrow @safe{
>     for(;;){}
> }

I'm not a fan of claiming to be cleverer than all future programmers. Allowing pure void(void) functions and making the compiler call them compulsively was my way of saying "hm, this is clever. This is too blatant of an error so I can only assume you wrote this function hoping it will be called. No idea what you do there, but I'm going to call it."

Apparently everybody else in this thread is clever enough so I'll eliminate the special case.


Andrei
December 21, 2016
On 12/20/2016 05:49 PM, Andrei Alexandrescu wrote:
> https://github.com/dlang/dlang.org/pull/1528 -- Andrei

Dropped the void functions. On to the next scandal:

>A function that accepts only parameters without mutable indirections and
>returns a result that has mutable indirections is called a $(I pure factory
>function). An implementation may assume that all mutable memory returned by
>the call is not referenced by any other part of the program, i.e. it is
>newly allocated by the function.


Andrei
December 21, 2016
On Wednesday, 21 December 2016 at 15:40:42 UTC, Andrei Alexandrescu wrote:
> On 12/20/2016 05:49 PM, Andrei Alexandrescu wrote:
>> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
>
> Dropped the void functions. On to the next scandal:
>
>>A function that accepts only parameters without mutable indirections and
>>returns a result that has mutable indirections is called a $(I pure factory
>>function). An implementation may assume that all mutable memory returned by
>>the call is not referenced by any other part of the program, i.e. it is
>>newly allocated by the function.
>
>
> Andrei

Couldn't this be folded into :
"The implementation may not remove a call to a pure function if does allocate memory ?"

Since there is the concept of weakly pure functions the compiler cannot decide to remove functions on signature alone.
Meaning the body has to be available for it to even attempt to elide the call.

Therefore specifying implementation behavior based on the function signature is misleading IMO.


December 21, 2016
On Wednesday, 21 December 2016 at 15:40:42 UTC, Andrei Alexandrescu wrote:
> On 12/20/2016 05:49 PM, Andrei Alexandrescu wrote:
>> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
>
> Dropped the void functions. On to the next scandal:

I think you should be very careful in making sure that `pure` does not become a pessimization keyword.

```
$(P Any `pure` function that is not strongly pure cannot be memoized. The
     compiler is required to honor all calls to the function, even if it appears
     to do nothing. (Example: a pure function taking no arguments and returning
     `int*` cannot be memoized. It also should not because it may be a typical
     factory function returning a fresh pointer with each call.))
```
I don't know what "required to honor all calls" means, but I guess it means
```
auto a = foo(); // int* foo() pure;
auto b = foo();
```
cannot be transformed to
```
auto a = foo(); // int* foo() pure;
auto b = a;
```

Super contrived, but I hope you get my drift:
```
int *awesome() pure {
  static if (ohSoAwesome) {
     return new int;
  } else {
     return null;
  }
}
```
Tagging this function with `pure` would be a pessimization.
Instead of
  "Any `pure` function that is not strongly pure cannot be memoized."
why not
  "Any `pure` function that is not strongly pure _may not be assumed to be_ memoizable."

Another example:
```
/// Note: need to mark this function as non-pure, because otherwise the compiler deduces it as pure and then pessimizes our code.
int *bar(bool returnNull) nonpure {
  if (returnNull) {
     return null;
  } else {
     return new ...;
  }
}

auto a = bar(true);
auto b = bar(true);
auto c = bar(true);
```

My concern with the current wording (like for the void function thing) is that it actively prohibits the compiler to do a transformation even if that is valid.

-Johan

December 21, 2016
On Wednesday, 21 December 2016 at 20:04:04 UTC, Johan Engelen wrote:
> 
>   "Any `pure` function that is not strongly pure _may not be assumed to be_ memoizable."

That version of mine is also not correct :(

How about: "A strongly pure function can be assumed to be memoizable. For a not strongly pure function, well, `pure` does not add information regarding memoizability."

December 21, 2016
On Wednesday, December 21, 2016 15:49:35 Stefan Koch via Digitalmars-d wrote:
> On Wednesday, 21 December 2016 at 15:40:42 UTC, Andrei
>
> Alexandrescu wrote:
> > On 12/20/2016 05:49 PM, Andrei Alexandrescu wrote:
> >> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
> >
> > Dropped the void functions. On to the next scandal:
> >>A function that accepts only parameters without mutable
> >>indirections and
> >>returns a result that has mutable indirections is called a $(I
> >>pure factory
> >>function). An implementation may assume that all mutable memory
> >>returned by
> >>the call is not referenced by any other part of the program,
> >>i.e. it is
> >>newly allocated by the function.
> >>
> > Andrei
>
> Couldn't this be folded into :
> "The implementation may not remove a call to a pure function if
> does allocate memory ?"
>
> Since there is the concept of weakly pure functions the compiler
> cannot decide to remove functions on signature alone.
> Meaning the body has to be available for it to even attempt to
> elide the call.
>
> Therefore specifying implementation behavior based on the function signature is misleading IMO.

Why would the function body need to be there to elide the call? Only calls to "strongly" pure functions can be elided when called multiple times, so "weak" purity doesn't enter into the equation. And how "strong" a pure function is has everything to do with its signature and nothing to do with its body. pure has always been designed with the idea that it would be the function signature that mattered. The body only comes into play when inferring purity.

What Andrei has put here is to codify what the compiler needs to look at to determine whether a strongly pure function may have allocated and returned that memory (or something that referred to that memory) and made it so that the compiler is not allowed to elide the call in that specific case. There is no need to specifically mention memory allocation unless you're looking to indicate why the spec is saying that such calls cannot be elided.

- Jonathan M Davis

December 21, 2016
On Wednesday, 21 December 2016 at 15:40:42 UTC, Andrei Alexandrescu wrote:
> On 12/20/2016 05:49 PM, Andrei Alexandrescu wrote:
>> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
>
> Dropped the void functions. On to the next scandal:
>
>>A function that accepts only parameters without mutable indirections and
>>returns a result that has mutable indirections is called a $(I pure factory
>>function). An implementation may assume that all mutable memory returned by
>>the call is not referenced by any other part of the program, i.e. it is
>>newly allocated by the function.
>
>
> Andrei

There are 3 levels:
1) no idea what's going on: e.g. the function returns a mutable reference and also reads from global mutable memory.
2) memory must be new: e.g. returns 2 mutable references, no accessing external mutable memory.
3) memory must be new and uniquely referenced: function returns 1 mutable reference, does not access external mutable memory.

If I'm not mistaken only 3 enables anything useful like implicit casts to immutable.

Also, "returned references" should be extended to include "out" parameters, because there's no difference as far as memory uniqueness is concerned.
December 21, 2016
On 12/21/2016 03:04 PM, Johan Engelen wrote:
> On Wednesday, 21 December 2016 at 15:40:42 UTC, Andrei Alexandrescu wrote:
>> On 12/20/2016 05:49 PM, Andrei Alexandrescu wrote:
>>> https://github.com/dlang/dlang.org/pull/1528 -- Andrei
>>
>> Dropped the void functions. On to the next scandal:
>
> I think you should be very careful in making sure that `pure` does not
> become a pessimization keyword.
>
> ```
> $(P Any `pure` function that is not strongly pure cannot be memoized. The
>      compiler is required to honor all calls to the function, even if it
> appears
>      to do nothing. (Example: a pure function taking no arguments and
> returning
>      `int*` cannot be memoized. It also should not because it may be a
> typical
>      factory function returning a fresh pointer with each call.))
> ```
> I don't know what "required to honor all calls" means, but I guess it means
> ```
> auto a = foo(); // int* foo() pure;
> auto b = foo();
> ```
> cannot be transformed to
> ```
> auto a = foo(); // int* foo() pure;
> auto b = a;
> ```

That is correct.

> Super contrived, but I hope you get my drift:
> ```
> int *awesome() pure {
>   static if (ohSoAwesome) {
>      return new int;
>   } else {
>      return null;
>   }
> }
> ```

Where does ohSoAwesome come from?

> Tagging this function with `pure` would be a pessimization.

Not to worry. It's all up to what can be detected. If inlining is in effect then definitely things can be optimized appropriately.

> Instead of
>   "Any `pure` function that is not strongly pure cannot be memoized."
> why not
>   "Any `pure` function that is not strongly pure _may not be assumed to
> be_ memoizable."

Got it. Good point. Will do.

> Another example:
> ```
> /// Note: need to mark this function as non-pure, because otherwise the
> compiler deduces it as pure and then pessimizes our code.
> int *bar(bool returnNull) nonpure {
>   if (returnNull) {
>      return null;
>   } else {
>      return new ...;
>   }
> }
>
> auto a = bar(true);
> auto b = bar(true);
> auto c = bar(true);
> ```
>
> My concern with the current wording (like for the void function thing)
> is that it actively prohibits the compiler to do a transformation even
> if that is valid.

Yah, we kind of assume without stating that whole "observable behavior" that C++ does.


Andrei


1 2 3 4 5