June 07, 2012
On 06/07/12 21:55, Steven Schveighoffer wrote:
> On Thu, 07 Jun 2012 15:16:20 -0400, Artur Skawina <art.08.09@gmail.com> wrote:
> 
>> On 06/07/12 20:29, Steven Schveighoffer wrote:
> 
>>> I'm not proposing disallowing mutable references, just shared references.
>>
>> I know, but if a D function marked as "pure" takes a mutable ref (which a shared one has to be assumed to be), it won't be treated as really pure for optimization purposes (yes, i'm deliberately trying to avoid "strong" and "weak").
> 
> However, a mutable pure function can be *inside* an optimizable pure function, and the optimizable function can still be optimized.
> 
> A PAS function (pure accepting shared), however, devolves to a mutable pure function.  That is, there is zero advantage of having a pure function take shared vs. simply mutable TLS.
> 
> There is only one reason to mark a function that does not take all immutable or value type arguments as pure -- so it can be called inside a strong-pure function.  Otherwise, it's just a normal function, and even marked as pure will not be optimized.  You gain nothing else by marking it pure.
> 
> So let's look at two cases.  I'll re-state my example, in terms of two overloads, one which takes shared int and one which takes just int (both of which do the right thing):
> 
> void inc(ref int t) pure;
> {
>   ++t;
> }
> 
> void inc(ref shared(int) t) pure
> {
>   atomicOp!"++"(t);
> }
> 
> Now, let's define a strong-pure function that uses inc:
> 
> int slowAdd(int x, int y) pure
> {
>    while(y--) inc(x);
>    return x;
> }
> 
> I think we can both agree that inc *cannot* be optimized away, and that we agree slowAdd is *fully pure*.  That is, slowAdd *can* be optimized away, even though its call to inc cannot.
> 
> Now, what about a strong-pure function using the second (shared) form?  A strong pure function has to have all parameters (and return types) that are immutable or implicitly convertable to immutable.
> 
> I'll re-define slowAdd:
> 
> int slowAddShared(int x, int y) pure
> {
>    shared int sx = x;
>    while(y--) inc(sx);
>    return sx;
> }
> 
> We can agree for the same reason the original slowAdd is strong-pure, slowAddShared is strong-pure.
> 
> But what do we gain by being able to declare sx shared?  We can't return it as shared, or slowAddShared becomes weak-pure.

Actually, *value* return types shouldn't prevent the function from being pure. But there is not much point in returning them as shared, other than to avoid explicit casts, something that would better solved with some kind of 'unique' class.


> We can't share it while inside slowAddShared, because we have no outlet for it, and we cannot access global variables.  In essence, marking sx as shared does *nothing*.  In fact, it does worse than nothing -- we now have to contend with shared for data that actually is *provably* unshared.  In other words, we are wasting cycles doing atomic operations instead of straight ops on a shared type.  Not only that, but because there are no outlets, declaring *any* data as shared while inside a strong-pure function is useless, no matter how we define any PAS functions.
> 
> So if shared is useless inside a strong-pure function, and the only point in marking a non-pure-optimizable function as pure is so it can be called within a strong-pure function, then pure is useless as an attribute on a function that accepts or returns shared data.  *Every case* where you use such a function inside a strong-pure function is incorrect.

We clearly agree completely; this is exactly what I'm saying in the paragraph you quoted below. What i'm *also* saying is that the 'incorrectness' of it is harmless in practice - so I'm not sure that it should be forbidden, and handled specially (which would be necessary in the inferred-purity cases).


>> And any caller
>> will have to obtain this shared ref either from a mutable argument or global state.
>> Hence that "pure" function with shared inputs will *never* actually be pure.
>> So I'm wondering what would be the gain from banning shared in weakly pure functions
> 
> What is to gain is clarity, and more control over parameter types in generic code.
> 
> If shared is banned, than:
> 
> void inc(T)(ref T t) pure { ++t; }
> 
> *always* does the right thing.  As the author of inc, I am done.  I don't need template constraints or documentation, or anything else, and I don't need to worry about users abusing my function.  The compiler will enforce nobody uses this on shared data, which would require an atomic operation.

Having a type that allows operators that are either illegal or wrongly implemented is not a problem specific to pure functions. My argument is that 'shared int' as a type is worthless and should never appear in real code. The 'Atomic!int' example was not made up - it is a real template used in my code that only allows legal operations. That first 'inc' example would end up using

      pragma(attribute, always_inline) void opOpAssign(string op:"+")(size_t n) {
         asm { "lock add"~opsuffix~" %1, %0 #ATOMIC_ADD" : "+m" data : "ir" n*unitsize ; }
      }

and work correctly. I don't think it makes sense to worry about using built-in types marked as shared directly, that is not likely to do the right thing; in fact using shared(T) should probably be forbidden for every T that can not guarantee every operation on it to be correct and always safe.

(oh, and that opOpAssign is intentionally not marked as pure, but I should probably check what the compiler does; when i wrote it, I was assuming that the shared 'this', shared 'data' and lack of outputs would make it do the right thing)


>> (Ugh, you made me use that word after all ;) ).
> 
> I did nothing of the sort :)
> 
>> AFAICT you're proposing to forbid something which currently is a NOOP.
> 
> It's not a NOOP, marking something as shared means you need special handling.  You can't call most functions or methods with shared data.  And if you do handle shared data, it's not just "the same" as unshared data -- you need to contend with data races, memory barriers, etc.  Just because it's marked shared doesn't mean everything about it is handled.

Exactly, see above. That's why you never access "raw" shared data - you always wrap it.
("access" meaning read and/or write, passing refs around is fine)
Problem solved.


>> And the change
>> could have consequences for templated functions or lambdas, where "pure" is inferred.
> 
> I would label those as *helpful* and *positive* consequences ;)

Are you saying that

   auto f(T)(T v) { return v+v; }

should be inferred as impure when used with a shared(T), but (weakly) pure
otherwise?

artur
June 07, 2012
On Thu, 07 Jun 2012 17:36:45 -0400, Artur Skawina <art.08.09@gmail.com> wrote:

> On 06/07/12 21:55, Steven Schveighoffer wrote:
>> On Thu, 07 Jun 2012 15:16:20 -0400, Artur Skawina <art.08.09@gmail.com> wrote:
>>
>>> On 06/07/12 20:29, Steven Schveighoffer wrote:
>>
>>>> I'm not proposing disallowing mutable references, just shared references.
>>>
>>> I know, but if a D function marked as "pure" takes a mutable ref (which a shared
>>> one has to be assumed to be), it won't be treated as really pure for optimization
>>> purposes (yes, i'm deliberately trying to avoid "strong" and "weak").
>>
>> However, a mutable pure function can be *inside* an optimizable pure function, and the optimizable function can still be optimized.
>>
>> A PAS function (pure accepting shared), however, devolves to a mutable pure function.  That is, there is zero advantage of having a pure function take shared vs. simply mutable TLS.
>>
>> There is only one reason to mark a function that does not take all immutable or value type arguments as pure -- so it can be called inside a strong-pure function.  Otherwise, it's just a normal function, and even marked as pure will not be optimized.  You gain nothing else by marking it pure.
>>
>> So let's look at two cases.  I'll re-state my example, in terms of two overloads, one which takes shared int and one which takes just int (both of which do the right thing):
>>
>> void inc(ref int t) pure;
>> {
>>   ++t;
>> }
>>
>> void inc(ref shared(int) t) pure
>> {
>>   atomicOp!"++"(t);
>> }
>>
>> Now, let's define a strong-pure function that uses inc:
>>
>> int slowAdd(int x, int y) pure
>> {
>>    while(y--) inc(x);
>>    return x;
>> }
>>
>> I think we can both agree that inc *cannot* be optimized away, and that we agree slowAdd is *fully pure*.  That is, slowAdd *can* be optimized away, even though its call to inc cannot.
>>
>> Now, what about a strong-pure function using the second (shared) form?  A strong pure function has to have all parameters (and return types) that are immutable or implicitly convertable to immutable.
>>
>> I'll re-define slowAdd:
>>
>> int slowAddShared(int x, int y) pure
>> {
>>    shared int sx = x;
>>    while(y--) inc(sx);
>>    return sx;
>> }
>>
>> We can agree for the same reason the original slowAdd is strong-pure, slowAddShared is strong-pure.
>>
>> But what do we gain by being able to declare sx shared?  We can't return it as shared, or slowAddShared becomes weak-pure.
>
> Actually, *value* return types shouldn't prevent the function from being pure. But
> there is not much point in returning them as shared, other than to avoid explicit
> casts, something that would better solved with some kind of 'unique' class.

Right, what I meant was, returning a shared reference.  For example, if a pure function allocated memory and returned it as a shared pointer, that would make it non-optimizable pure (weak pure).

>> We can't share it while inside slowAddShared, because we have no outlet for it, and we cannot access global variables.  In essence, marking sx as shared does *nothing*.  In fact, it does worse than nothing -- we now have to contend with shared for data that actually is *provably* unshared.  In other words, we are wasting cycles doing atomic operations instead of straight ops on a shared type.  Not only that, but because there are no outlets, declaring *any* data as shared while inside a strong-pure function is useless, no matter how we define any PAS functions.
>>
>> So if shared is useless inside a strong-pure function, and the only point in marking a non-pure-optimizable function as pure is so it can be called within a strong-pure function, then pure is useless as an attribute on a function that accepts or returns shared data.  *Every case* where you use such a function inside a strong-pure function is incorrect.
>
> We clearly agree completely; this is exactly what I'm saying in the paragraph you
> quoted below. What i'm *also* saying is that the 'incorrectness' of it is harmless
> in practice - so I'm not sure that it should be forbidden, and handled specially
> (which would be necessary in the inferred-purity cases).

I have given you an example of where it is harmful.  There is benefit in being able to say "since I marked this function pure, I know I don't have to deal with threading."  It allows you to eliminate possible multi-threading mistakes from whole swaths of code, especial generic code which accepts a myriad of types.

You know there are a ton of generic functions in phobos that don't check *at all* whether shared data is being given to them?  Simply marking them pure (which should be viable for most functions) would eliminate that worry.

>>> And any caller
>>> will have to obtain this shared ref either from a mutable argument or global state.
>>> Hence that "pure" function with shared inputs will *never* actually be pure.
>>> So I'm wondering what would be the gain from banning shared in weakly pure functions
>>
>> What is to gain is clarity, and more control over parameter types in generic code.
>>
>> If shared is banned, than:
>>
>> void inc(T)(ref T t) pure { ++t; }
>>
>> *always* does the right thing.  As the author of inc, I am done.  I don't need template constraints or documentation, or anything else, and I don't need to worry about users abusing my function.  The compiler will enforce nobody uses this on shared data, which would require an atomic operation.
>
> Having a type that allows operators that are either illegal or wrongly implemented
> is not a problem specific to pure functions. My argument is that 'shared int' as
> a type is worthless and should never appear in real code. The 'Atomic!int' example
> was not made up - it is a real template used in my code that only allows legal
> operations. That first 'inc' example would end up using
>
>       pragma(attribute, always_inline) void opOpAssign(string op:"+")(size_t n) {
>          asm { "lock add"~opsuffix~" %1, %0 #ATOMIC_ADD" : "+m" data : "ir" n*unitsize ; }
>       }
>
> and work correctly. I don't think it makes sense to worry about using built-in
> types marked as shared directly, that is not likely to do the right thing; in fact
> using shared(T) should probably be forbidden for every T that can not guarantee
> every operation on it to be correct and always safe.

I would be in favor of that.  Right now the huge benefit of shared is what you can assume on stuff that's *not* marked as shared.  Using actual shared types is very cumbersome and difficult to understand.

Giving shared more useful and robust meaning would be a huge benefit.

> (oh, and that opOpAssign is intentionally not marked as pure, but I should probably
> check what the compiler does; when i wrote it, I was assuming that the shared 'this',
> shared 'data' and lack of outputs would make it do the right thing)

marking it as pure is like putting static on a class.  It will achieve nothing.

>>> AFAICT you're proposing to forbid something which currently is a NOOP.
>>
>> It's not a NOOP, marking something as shared means you need special handling.  You can't call most functions or methods with shared data.  And if you do handle shared data, it's not just "the same" as unshared data -- you need to contend with data races, memory barriers, etc.  Just because it's marked shared doesn't mean everything about it is handled.
>
> Exactly, see above. That's why you never access "raw" shared data - you always wrap it.
> ("access" meaning read and/or write, passing refs around is fine)
> Problem solved.

Let's not forget the main benefit of pure -- to allow optimization.  Marking something as optimizable that *can never be* optimized or be a part of *any* optimizable function serves no purpose.

Let's not forget a secondary benefit of pure -- dispatchability (probably a better term for this).  If I know there's no shared data involved, I can dispatch a pure function to another worker thread without worry of races, especially a strong-pure function, but it's quite easy to prove validity for a weak-pure function.

If shared is involved, the second aspect goes out the window.

>>> And the change
>>> could have consequences for templated functions or lambdas, where "pure" is inferred.
>>
>> I would label those as *helpful* and *positive* consequences ;)
>
> Are you saying that
>
>    auto f(T)(T v) { return v+v; }
>
> should be inferred as impure when used with a shared(T), but (weakly) pure
> otherwise?

You are saying two different things here...
f's purity depends on the expression (v + v)'s purity.  And the level of purity (weak or strong) depends on the level of (v + v)'s purity.  IF v + v is strong-pure (such as int + int), then f is strong-pure.  If v + v is weak-pure, f is weak pure.  If v + v is not pure, then f is not pure.  That is how it works today.

What I'm saying is, shared just shouldn't be allowed to be any part of pure.  So if T is defined as shared int, even though it actually makes no sense whatsoever for your example, f will be unpure.

That's another aspect of shared that needs to be addressed -- type inference for shared expressions.

for instance:

shared int x, y;

auto z = x + y;

What type should z be?  Right now it's shared, but that makes *no* sense, because z is not shared until you share it.  Why should auto opt-in to something it doesn't have to?

Likewise with IFTI, f(x) should probably equate to f!int(x) (in which case it *would* be pure)

-Steve
June 08, 2012
On 06/08/12 00:42, Steven Schveighoffer wrote:
> On Thu, 07 Jun 2012 17:36:45 -0400, Artur Skawina <art.08.09@gmail.com> wrote:
> 
>> On 06/07/12 21:55, Steven Schveighoffer wrote:
>>> On Thu, 07 Jun 2012 15:16:20 -0400, Artur Skawina <art.08.09@gmail.com> wrote:
>>>
>>>> On 06/07/12 20:29, Steven Schveighoffer wrote:
>>>
>>>>> I'm not proposing disallowing mutable references, just shared references.
>>>>
>>>> I know, but if a D function marked as "pure" takes a mutable ref (which a shared one has to be assumed to be), it won't be treated as really pure for optimization purposes (yes, i'm deliberately trying to avoid "strong" and "weak").
>>>
>>> However, a mutable pure function can be *inside* an optimizable pure function, and the optimizable function can still be optimized.
>>>
>>> A PAS function (pure accepting shared), however, devolves to a mutable pure function.  That is, there is zero advantage of having a pure function take shared vs. simply mutable TLS.
>>>
>>> There is only one reason to mark a function that does not take all immutable or value type arguments as pure -- so it can be called inside a strong-pure function.  Otherwise, it's just a normal function, and even marked as pure will not be optimized.  You gain nothing else by marking it pure.
>>>
>>> So let's look at two cases.  I'll re-state my example, in terms of two overloads, one which takes shared int and one which takes just int (both of which do the right thing):
>>>
>>> void inc(ref int t) pure;
>>> {
>>>   ++t;
>>> }
>>>
>>> void inc(ref shared(int) t) pure
>>> {
>>>   atomicOp!"++"(t);
>>> }
>>>
>>> Now, let's define a strong-pure function that uses inc:
>>>
>>> int slowAdd(int x, int y) pure
>>> {
>>>    while(y--) inc(x);
>>>    return x;
>>> }
>>>
>>> I think we can both agree that inc *cannot* be optimized away, and that we agree slowAdd is *fully pure*.  That is, slowAdd *can* be optimized away, even though its call to inc cannot.
>>>
>>> Now, what about a strong-pure function using the second (shared) form?  A strong pure function has to have all parameters (and return types) that are immutable or implicitly convertable to immutable.
>>>
>>> I'll re-define slowAdd:
>>>
>>> int slowAddShared(int x, int y) pure
>>> {
>>>    shared int sx = x;
>>>    while(y--) inc(sx);
>>>    return sx;
>>> }
>>>
>>> We can agree for the same reason the original slowAdd is strong-pure, slowAddShared is strong-pure.
>>>
>>> But what do we gain by being able to declare sx shared?  We can't return it as shared, or slowAddShared becomes weak-pure.
>>
>> Actually, *value* return types shouldn't prevent the function from being pure. But there is not much point in returning them as shared, other than to avoid explicit casts, something that would better solved with some kind of 'unique' class.
> 
> Right, what I meant was, returning a shared reference.  For example, if a pure function allocated memory and returned it as a shared pointer, that would make it non-optimizable pure (weak pure).
> 
>>> We can't share it while inside slowAddShared, because we have no outlet for it, and we cannot access global variables.  In essence, marking sx as shared does *nothing*.  In fact, it does worse than nothing -- we now have to contend with shared for data that actually is *provably* unshared.  In other words, we are wasting cycles doing atomic operations instead of straight ops on a shared type.  Not only that, but because there are no outlets, declaring *any* data as shared while inside a strong-pure function is useless, no matter how we define any PAS functions.
>>>
>>> So if shared is useless inside a strong-pure function, and the only point in marking a non-pure-optimizable function as pure is so it can be called within a strong-pure function, then pure is useless as an attribute on a function that accepts or returns shared data.  *Every case* where you use such a function inside a strong-pure function is incorrect.
>>
>> We clearly agree completely; this is exactly what I'm saying in the paragraph you quoted below. What i'm *also* saying is that the 'incorrectness' of it is harmless in practice - so I'm not sure that it should be forbidden, and handled specially (which would be necessary in the inferred-purity cases).
> 
> I have given you an example of where it is harmful.  There is benefit in being able to say "since I marked this function pure, I know I don't have to deal with threading."  It allows you to eliminate possible multi-threading mistakes from whole swaths of code, especial generic code which accepts a myriad of types.
> 
> You know there are a ton of generic functions in phobos that don't check *at all* whether shared data is being given to them?  Simply marking them pure (which should be viable for most functions) would eliminate that worry.

I can see certain generic functions being useful when working with shared data too. Yes, they can be used incorrectly, but I'd expect anybody working with shared to know what they're doing. I see your point, but I'm not convinced that this reason alone is enough to also disallow legal uses (and, no, I don't think I've ever used 'shared' in this way with phobos, nor can I think of "legal" examples right now).


>>>> AFAICT you're proposing to forbid something which currently is a NOOP.
>>>
>>> It's not a NOOP, marking something as shared means you need special handling.  You can't call most functions or methods with shared data.  And if you do handle shared data, it's not just "the same" as unshared data -- you need to contend with data races, memory barriers, etc.  Just because it's marked shared doesn't mean everything about it is handled.
>>
>> Exactly, see above. That's why you never access "raw" shared data - you always wrap it.
>> ("access" meaning read and/or write, passing refs around is fine)
>> Problem solved.
> 
> Let's not forget the main benefit of pure -- to allow optimization.  Marking something as optimizable that *can never be* optimized or be a part of *any* optimizable function serves no purpose.

That's why I'm saying it's a NOOP. Forbidding it can avoid certain type of bugs, yes, but I'd argue that the real cause of these bugs is accessing shared data in unsafe ways at all - disallowing this /just/ in pure functions does not seem like much of an improvement.


> Let's not forget a secondary benefit of pure -- dispatchability (probably a better term for this).  If I know there's no shared data involved, I can dispatch a pure function to another worker thread without worry of races, especially a strong-pure function, but it's quite easy to prove validity for a weak-pure function.
> 
> If shared is involved, the second aspect goes out the window.

Hmm. Not necessarily, if shared is done right. But I don't think these types of optimizations really work in practice, except when explicitly requested (by annotating the code in some way, so that the compiler knows where applying them makes sense).


>>>> And the change
>>>> could have consequences for templated functions or lambdas, where "pure" is inferred.
>>>
>>> I would label those as *helpful* and *positive* consequences ;)
>>
>> Are you saying that
>>
>>    auto f(T)(T v) { return v+v; }
>>
>> should be inferred as impure when used with a shared(T), but (weakly) pure
>> otherwise?
> 
> You are saying two different things here...

I meant T to be a reference type, should have been more explicit about that, sorry.

> f's purity depends on the expression (v + v)'s purity.  And the level of purity (weak or strong) depends on the level of (v + v)'s purity.  IF v + v is strong-pure (such as int + int), then f is strong-pure.  If v + v is weak-pure, f is weak pure.  If v + v is not pure, then f is not pure.  That is how it works today.
> 
> What I'm saying is, shared just shouldn't be allowed to be any part of pure.  So if T is defined as shared int, even though it actually makes no sense whatsoever for your example, f will be unpure.

I agree, that is the sane approach.

Well, at least with the current 'shared' definition, which implies C-style
'volatile' (which i think should be a separate attribute, there are cases
where it's not necessary).
And a function with no refs in the signature should not be prevented from
being pure, except when it accesses global state, but that's obvious.


> That's another aspect of shared that needs to be addressed -- type inference for shared expressions.
> 
> for instance:
> 
> shared int x, y;
> 
> auto z = x + y;
> 
> What type should z be?  Right now it's shared, but that makes *no* sense, because z is not shared until you share it.  Why should auto opt-in to something it doesn't have to?

Yep, but there is no good solutions to that right now. I know, "polysemous",
but that would *not* solve the problem, at least not w/o the type of 'z'
remaining in that state until it is actually used.
But now we're reinventing 'uniq' again. :)


> Likewise with IFTI, f(x) should probably equate to f!int(x) (in which case it *would* be pure)

Hmm, shared(T) should never implicitly convert to (T), it *is* safe when
T==int, but the loss of type info could cause problems. If you meant f(z),
then yes that would work, but 'z' remaining in that polysemous state would
be even better.


Hmm, the remaining question seems to be whether

   auto f(T)(T v) pure { return v+v; }

should accept a "shared" T or not.

And I actually think that it shouldn't, for any reasonable interpretation of function purity. Except D's "weak" purity combined with the ill-defined shared semantics complicates things and makes the answer less obvious. I'm starting to feel like I'm playing the devil's advocate here. :)


artur
June 08, 2012
On Thursday, June 07, 2012 10:43:04 Steven Schveighoffer wrote:
> On Wed, 06 Jun 2012 19:01:59 -0400, Alex Rønne Petersen <alex@lycus.org>
> 
> wrote:
> > Steven, in your particular case, I don't agree entirely. The operation
> > can be atomic quite trivially by implementing inc() like so (for the
> > shared int case):
> > 
> > void inc(ref shared int i) pure nothrow
> > {
> > 
> > // just pretend the compiler emitted this
> > asm
> > {
> > 
> > mov EDX, i;
> > lock;
> > inc [EDX];
> > 
> > }
> > 
> > }
> > 
> > But I may be misunderstanding you.
> 
> I think you are. I understand the implementation is not correct for shared, and that actually is my point. The current compiler lets you do the wrong thing without complaint. Given that the shared version of the function needs to be written differently than the unshared version, we gain nothing but bugs by allowing pure functions that operate on shared.
> 
> In essence, a pure-accepting-shared (PAS) function is not realistically useful from a strong-pure function. A strong-pure function will have no ties to shared data, and while it may be able to create data that could potentially be shared, it can't actually share it! So a PAS function being called from a strong-pure function is essentially doing extra work (even if it's not implemented, the expectation is it will be some day) for no reason.
> 
> So since a PAS function cannot usefully be optimized (much better to write an unshared version, it's more accurate), and must be written separately from the unshared version, I see no good reason to allow shared in pure functions ever. I think we gain a lot by not allowing it (more sanity for one thing!)

But what does it matter if a PAS function can be called from a strongly pure function? If it can't be called from a strongly pure function anyway, then you don't have to worry about shared being used in a strongly pure function regardless of whether pure is permitted in weakly pure functions.

However, if shared is banned from pure functions, then you can't mark any function which would otherwise be pure as pure. As it stands, you can have

auto func(T)(T stuff) pure {}

where if you pass it non-shared, it can be used in a strongly pure function with all of its benefits, _and_ you can call it with shared (in which case, it can't be called in a strongly pure function, but that doesn't mean that function isn't useful). If shared were not permitted in pure functions, then you'd have to do

auto func(T)(T stuff) {}

in which case, func could be used with impure, non-shared stuff, which the previous signature prevented. To avoid that, you have to duplicate the function - one version for shared and one without.

In addition, if you have a PAS function, it has the guarantee that it won't access anything mutable that wasn't passed to it. If you make PAS illegal, then there is _no_ way to have that guarantee for shared.

I don't see how allowing shared in pure functions costs us _anything_. No, such a function can't be used in a strongly pure function, but you still have the guarantee about mutable globals (which is definitely worth something), and you avoid having to duplicate the function for shared vs non-shared. And since it _can't_ be called from a strongly pure function, you don't have to worry about the fact that multiple calls to such a function can result in different return values due to another thread altering the variable.

As far as I can tell, making PAS functions illegal just makes life harder and buys us nothing. Strongly pure is where the optimization benefits of shared are, and PAS functions don't prevent that at all - they just can't gain from it.

It seems like your issue with PAS stems primarily from the idea that such a weakly pure function can't be called from a strongly pure function, and I don't see why that's really a problem - particularly given that that's not pure's only benefit and is arguably the _smaller_ of its benefits given how rarely it can actually be applied.

- Jonathan M Davis
June 08, 2012
>>> >>We clearly agree completely; this is exactly what I'm saying in the paragraph you
>>> >>quoted below. What i'm*also*  saying is that the 'incorrectness' of it is harmless
>>> >>in practice - so I'm not sure that it should be forbidden, and handled specially
>>> >>(which would be necessary in the inferred-purity cases).

but it makes no sense to cripple an feature like pure half-way - pure is clean and well defined (still not perfect) - but you talking about making it very stupid and sensless "'incorrectness' of it is harmless
in practice" ... "Yes, they can be used incorrectly, but I'd expect anybody working with shared to know what they're doing" - no they don't - sorry, haven't you any real experience in the threading world or why don't you see the problems introdcing shared in pure
June 08, 2012
On 08-06-2012 09:00, dennis luehring wrote:
>>>> >>We clearly agree completely; this is exactly what I'm saying in
>>>> the paragraph you
>>>> >>quoted below. What i'm*also* saying is that the 'incorrectness' of
>>>> it is harmless
>>>> >>in practice - so I'm not sure that it should be forbidden, and
>>>> handled specially
>>>> >>(which would be necessary in the inferred-purity cases).
>
> but it makes no sense to cripple an feature like pure half-way - pure is
> clean and well defined (still not perfect) - but you talking about
> making it very stupid and sensless "'incorrectness' of it is harmless
> in practice" ... "Yes, they can be used incorrectly, but I'd expect
> anybody working with shared to know what they're doing" - no they don't
> - sorry, haven't you any real experience in the threading world or why
> don't you see the problems introdcing shared in pure

Without taking either side, I'm just gonna point out that this post is very incomprehensible and doesn't seem to bring any actual argument to the table.

-- 
Alex Rønne Petersen
alex@lycus.org
http://lycus.org
June 08, 2012
On 06/08/12 09:00, dennis luehring wrote:
>>>> >>We clearly agree completely; this is exactly what I'm saying in the paragraph you quoted below. What i'm*also*  saying is that the 'incorrectness' of it is harmless in practice - so I'm not sure that it should be forbidden, and handled specially (which would be necessary in the inferred-purity cases).
> 
> but it makes no sense to cripple an feature like pure half-way - pure is clean and well defined (still not perfect) - but you talking about making it very stupid and sensless "'incorrectness' of it is harmless
> in practice" ...

Marking a function that takes a pointer or reference which is not immutable does *not*
make it pure, it only allows it to be called from another pure function, that's all.
That's how D implements purity - if you think that's wrong and/or misleading, I agree,
but that's how it is.
'int f(shared T*)' can be tagged as pure, but can *never* actually be pure, because its
caller can never be pure. That's why allowing it to be "pure" is harmless, even if it
looks "incorrect" to have pure functions dealing with shared. It's a consequence of D's
current purity model, and, yes, it is confusing.

> "Yes, they can be used incorrectly, but I'd expect anybody working with shared to know what they're doing" - no they don't - sorry, haven't you any real experience in the threading world or why don't you see the problems introdcing shared in pure

The only way to (relatively) safely deal with raw (ie builtin) shared types it to treat 'shared' as the D equivalent of C's 'volatile'. Anything else won't correctly, and, no, it can't be made to work, attempting that would make 'shared' even less useful.

Shared data in pure code makes no sense, yes. D's "pure" is not the same as sanely defined "pure", however.

artur
1 2 3
Next ›   Last »