On Thu, 15 May 2014 10:10:57 +0000
via Digitalmars-d <digitalmars-d@puremagic.com> wrote:

> On Thursday, 15 May 2014 at 09:23:00 UTC, Jonathan M Davis via
> Digitalmars-d wrote:
> > functions that weren't pure. It allowed for mutation within the
> > function, and
> > it allowed for allocation via new, but from the outside, the
> > function _was_
> > functionally pure.
>
> If it didn't return the memory allocated with new and if the call
> to new resulted in an exception, yes.
>
> > It just didn't work.
>
> That I question. A pure function (
> http://en.wikipedia.org/wiki/Pure_function ) depends on the
> values of the parameters, and only that. That is most useful.
> Those value can be very complex. You could have a pure member
> function look up values in a cache. Then the configuration of
> entire cache is the value.
>
> You need to think about this in terms of pre/post conditions in
> Hoare Logic (which I am not very good at btw).
>
>
> > So, Don introduced the idea of "weak" purity. What it comes
> > down to is that
> > it's an extension of the concept that mutation within a pure
> > function is fine
> > just so long as its arguments aren't mutated. We made it so
> > that pure
> > functions _didn't_ have to have immutable parameters. They just
> > couldn't
> > access anything that wasn't passed to them as arguments. This
> > meant that they
> > could only mutate what they were given and thus they didn't
> > violate the
> > "strong" purity of the original pure function which had
> > immutable parameters.
>
> And that's fine as long as nobody else is holding a reference to
> those mutable parameters.

That would only matter if the compiler were trying to optimize based on pure
functions with mutable parameters. It doesn't. And it would actually be very
difficult for it to do so without doing full program optimization, which
really doesn't work with the C linking model that D uses. The fact that we
have thread-local by default helps, but it's not enough for more than very
simple cases.

The compiler doesn't care about optimizing weakly pure functions. The whole
purpose of weakly pure functions is to have functions which aren't
functionally pure but can still be used in functions that _are_ functionally
pure.

> If you think in terms of a context for purity such as a
> transaction then you can even allow access to globals as long as
> they remain constant until the transaction is committed (or you
> leave the context where purity is desired). Meaning, you can
> memoize within that context.

That doesn't work with D's model, because it doesn't have any concept of
transactions like that. It also doesn't really have any concept of
memoization either. The most that it does that is anything like memoizaton is
optimize away multiple calls to a function within a single expression (or
maybe within a statement - I don't remember which). So,

auto y = sqrt(x) * sqrt(x);

might become something more like

auto temp = sqrt(x);
y = x * x;

but after that, the result of sqrt(x) is forgotten. So, in reality, the
optimization gains from strongly pure functions are pretty minimal (almost
non-existent really). If we were willing to do code flow analysis, we could
probably make more optimizations (assuming that the exact same function call
was made several times within a single function, which isn't all that common
anyway), but Walter is generally against doing code flow analysis in the
compiler due to the complications that it adds. We have some, but not a lot.

The two main gains for purity are

1. being able to know for sure that a function doesn't access any global
   variables, which makes it easier to reason about the code.

2. being able to implicitly convert types to and from mutable, const, and
   immutable based on the knowledge that a particular value has to be unique.

I'd say that functional purity was really the original goal of adding pure to
the language, but it's really those two effects which have given us the most
benefit. #2 in particular was unexpected, and the compiler devs keep finding
new places that they can take advantage of it, which makes dealing with
immutable a lot more pleasant - particularly when it comes to creating
immutable objects that require mutation in order to be initialized properly.

> > functions that called it), but we do need that guarantee. The
> > result is that
> > the pure attribute doesn't in and of itself mean functional
> > purity anymore,
> > but it _can_ be used to build a function which is functionally
> > pure.
>
> But, that can be deduced by the compiler, so what is the point of
> having "pure" for "weakly pure"? Clearly you only need to specify
> "strongly pure"?

It can't be deduced from the signature, and the compiler has to be able to
know based only on the signature, because it doesn't necessarily have the
source code for the function available. The only functions for which the
compiler ever deduces anything from their bodies are templated functions,
because it always has their bodies, and if it didn't do the attribute
inference for you, you'd be forced to either restrict the function by marking
it (or not marking it) with a particular set of attributes, or you'd have to
duplicate the function for each combination of attributes that you wanted.

So, when you mark a function as pure, the compiler verifies that it's pure
based on its body, but when other functions use it, all they know or care
about is the signature of the function. So, it's up to the programmer to tell
the compiler that a function is supposed to be pure, and it's up to the
compiler to verify that, and then use that knowledge to infer other things
based purely on the signature (like functional purity or the ability to
implicitly convert a type to the same type but with different mutability).

> Ok, but maybe the opposite would be better. Marking functions
> that access globals with @global or something. After all, most
> functions don't access globals.

I agree that that would be great if we were starting over - just like it would
be great if @safe were the default, but it's too late now. We have to make do
with what we have. We can make backwards-compatible changes, but we're very
much trying to minimize breaking changes at this point. We'll still make them
if we really think that they're needed, but we really want to avoid it. And as
nice as changing some of the attributes around would be nice, it's too big a
breaking change for too little gain.

- Jonathan M Davis

Reply via email to