digitalmars.D.learn - Pure Contract bug? (unnecessarily strict)
- Era Scarecrow (11/11) Jan 28 2012 Maybe someone's brought this up, but i seem to have the compiler compla...
- Daniel Murphy (2/2) Jan 28 2012 The way to avoid purity checking is to put code in a debug {} statement....
- Jesse Phillips (7/10) Jan 30 2012 I don't see why this couldn't be done, not only does it get not
- Era Scarecrow (2/8) Jan 30 2012 Reported; Minor priority (Won't break code)
- Era Scarecrow (40/40) Feb 04 2012 If I'm reading how pure works, my original example was likely
- Timon Gehr (18/54) Feb 04 2012 Pure does not imply const in D. If you want stronger guarantees, just
- Era Scarecrow (35/53) Feb 04 2012 Only external data I was implying, that was not based off the
- Timon Gehr (12/64) Feb 04 2012 Yes. The compiler will only reorder/run in parallel/optimize if it is
- Era Scarecrow (9/22) Feb 04 2012 Even if you changed the signature of the pure function to 'pure
- Timon Gehr (3/24) Feb 04 2012 the signature I meant looks like
- Era Scarecrow (2/4) Feb 04 2012 Which then the only way you could call it, was if the object
- Timon Gehr (2/8) Feb 04 2012 Alternatively you can use pure int squaredPlus(int)const;, of course.
- David Held (18/34) Jan 02 2014 You can rewrite these like so:
- David Held (26/29) Jan 02 2014 I think this is a language defect:
- H. S. Teoh (72/107) Jan 02 2014 [...]
Maybe someone's brought this up, but i seem to have the compiler complaining
to me that my function isn't 'pure' by calling a non-pure function,
specifically to!string().
However the unpure functions are only accessed in the contracts, and only if
it failed seriously. Is this already planned to be worked on? I thought i read
the contracts shouldn't be considered as part of it since they are totally
excluded during the release builds (and shouldn't have any side effects).
Error: pure function 'offset' cannot call impure function 'to'
property const pure int offset(int field)
in {
assert(field < notes.length);
}
out (o) {
assert(o >= 0, "Negative value! Check structure:" ~ name ~ "\nReq:" ~ requ ~
"\nsize:" ~ to!string(size) ~ "\n");
}
body { ... }
Jan 28 2012
The way to avoid purity checking is to put code in a debug {} statement.
I'm not aware of any plans to disable purity checking for contracts.
Jan 28 2012
On Sunday, 29 January 2012 at 06:22:26 UTC, Era Scarecrow wrote:Maybe someone's brought this up, but i seem to have the compiler complaining to me that my function isn't 'pure' by calling a non-pure function, specifically to!string().I don't see why this couldn't be done, not only does it get not exist in release, it shouldn't be changing variables in non-release. As mentioned there is a hole for debug code. Report it: http://d.puremagic.com/issues/ and we'll see what happens with that.
Jan 30 2012
I don't see why this couldn't be done, not only does it get not exist in release, it shouldn't be changing variables in non-release. As mentioned there is a hole for debug code. Report it: http://d.puremagic.com/issues/ and we'll see what happens with that.Reported; Minor priority (Won't break code) http://d.puremagic.com/issues/show_bug.cgi?id=7401
Jan 30 2012
If I'm reading how pure works, my original example was likely
broken as it was part of a struct that returned a state value
(although the contract constraints meaning was still valid).
So is pure fully usable or is it not yet ready? Makes me think
that pure should have further constraints/limits, if it's part of
a class/struct it should either require or automatically be
static (no state access) and if it accesses any global variables,
they must be immutable.
int x;
immutable int y = 10;
pure int test(int z) {
int t = z + x; //should error
t += y; //fine, globally immutable.
return t;
}
struct X {
int s_x;
static int s_st_x;
immutable int s_y;
static immutable int s_st_y = 100;
pure int test(int z) {
int t = x + z; //should error
t += s_x; //error, mutable external state
t += s_st_x; //error, mutable external state (static)
t += s_y; //error, not statically immutable, mathematically
impure.
t += y; //fine, global immutable
t += s_st_y; //fine, statically immutable.
return t;
}
}
Errors I get currently are these:
test(int):
Error: pure function 'test' cannot access mutable static data 'x'
X.test(int):
Error: pure function 'test' cannot access mutable static data 'x'
Error: pure function 'test' cannot access mutable static data
's_st_x'
If I understand pure correctly, I should get two more, for s_x
and s_y.
Feb 04 2012
On 02/04/2012 08:51 PM, Era Scarecrow wrote:
If I'm reading how pure works, my original example was likely broken as
it was part of a struct that returned a state value (although the
contract constraints meaning was still valid).
So is pure fully usable or is it not yet ready? Makes me think that pure
should have further constraints/limits, if it's part of a class/struct
it should either require or automatically be static (no state access)
and if it accesses any global variables, they must be immutable.
int x;
immutable int y = 10;
pure int test(int z) {
int t = z + x; //should error
t += y; //fine, globally immutable.
return t;
}
struct X {
int s_x;
static int s_st_x;
immutable int s_y;
static immutable int s_st_y = 100;
pure int test(int z) {
int t = x + z; //should error
t += s_x; //error, mutable external state
t += s_st_x; //error, mutable external state (static)
t += s_y; //error, not statically immutable, mathematically impure.
t += y; //fine, global immutable
t += s_st_y; //fine, statically immutable.
return t;
}
}
Errors I get currently are these:
test(int):
Error: pure function 'test' cannot access mutable static data 'x'
X.test(int):
Error: pure function 'test' cannot access mutable static data 'x'
Error: pure function 'test' cannot access mutable static data 's_st_x'
If I understand pure correctly, I should get two more, for s_x and s_y.
Pure does not imply const in D. If you want stronger guarantees, just
turn 'test' into a const (or immutable) member function. In D, a
function can change anything that is mutable and reachable through its
parameters, this includes the implicit 'this' pointer. The reason for
this design is simple: There is no need to be overly restrictive,
because the additional restrictions are already trivially expressed in
the type system. Furthermore, any mutation of a parameter can be turned
into a less efficient protocol that only requires a const pure function,
so there would be no gain if pure implied const, it would only make pure
less useful.
void foo(int* x)pure{*x+=2;}
int bar(const(int)*x)pure{return *x+2;}
void main() pure{
int x, y;
foo(&x); // those two lines have
y = bar(&y); // equivalent functionality!
}
Feb 04 2012
Pure does not imply const in D. If you want stronger
guarantees, just turn 'test' into a const (or immutable) member
function. In D, a function can change anything that is mutable
and reachable through its parameters, this includes the
implicit 'this' pointer. The reason for this design is simple:
There is no need to be overly restrictive, because the
additional restrictions are already trivially expressed in the
type system. Furthermore, any mutation of a parameter can be
turned into a less efficient protocol that only requires a
const pure function, so there would be no gain if pure implied
const, it would only make pure less useful.
void foo(int* x)pure{*x+=2;}
int bar(const(int)*x)pure{return *x+2;}
void main() pure{
int x, y;
foo(&x); // those two lines have
y = bar(&y); // equivalent functionality!
}
Only external data I was implying, that was not based off the
input arguments. Examples in the book refer that calculating Pi,
or the square root of 2 is a constant and will always result in
the same output, regardless the situation.
Quote TDPL pg 165:
"In D, a function is considered pure if returning a result is
it's only effect and the result depends only on the function's
arguments.
Also, pure functions can run literally in parallel because they
don't interact with the rest of the program except through their
result."
So... If we take a struct.
struct X {
int i;
pure int squaredPlus(int x) {
return x*x + i
}
alias squaredPlus sqp;
}
X st(15);
writeln(st.sqp(0)); //15
int i1 = st.sqp(10); st.i++;
int i2 = st.sqp(10); st.i++;
int i3 = st.sqp(10); st.i++;
int i4 = st.sqp(10); st.i++;
assert(i1 == 100); //pass/fail?
assert(i2 == 101); //pass/fail?
assert(i3 == 102); //pass/fail?
assert(i4 == 103); //pass/fail?
assert(s1.i == 104); //probably pass.
If the compiler can reorder or run these in parallel (for
optimization) or caches the result the first time (since it's
suppose to always return the same value), what's correct in this
case? This afterall isn't synchronized, even if it was, what's
correct behavior? Am I wrong in understanding this?
Feb 04 2012
On 02/04/2012 11:04 PM, Era Scarecrow wrote:Probably the restriction was lifted after TDPL was out.Pure does not imply const in D. If you want stronger guarantees, just turn 'test' into a const (or immutable) member function. In D, a function can change anything that is mutable and reachable through its parameters, this includes the implicit 'this' pointer. The reason for this design is simple: There is no need to be overly restrictive, because the additional restrictions are already trivially expressed in the type system. Furthermore, any mutation of a parameter can be turned into a less efficient protocol that only requires a const pure function, so there would be no gain if pure implied const, it would only make pure less useful. void foo(int* x)pure{*x+=2;} int bar(const(int)*x)pure{return *x+2;} void main() pure{ int x, y; foo(&x); // those two lines have y = bar(&y); // equivalent functionality! }Only external data I was implying, that was not based off the input arguments. Examples in the book refer that calculating Pi, or the square root of 2 is a constant and will always result in the same output, regardless the situation. Quote TDPL pg 165: "In D, a function is considered pure if returning a result is it's only effect and the result depends only on the function's arguments. Also, pure functions can run literally in parallel because they don't interact with the rest of the program except through their result."So... If we take a struct. struct X { int i; pure int squaredPlus(int x) { return x*x + i } alias squaredPlus sqp; } X st(15); writeln(st.sqp(0)); //15 int i1 = st.sqp(10); st.i++; int i2 = st.sqp(10); st.i++; int i3 = st.sqp(10); st.i++; int i4 = st.sqp(10); st.i++; assert(i1 == 100); //pass/fail? assert(i2 == 101); //pass/fail? assert(i3 == 102); //pass/fail? assert(i4 == 103); //pass/fail? assert(s1.i == 104); //probably pass. If the compiler can reorder or run these in parallel (for optimization) or caches the result the first time (since it's suppose to always return the same value), what's correct in this case? This afterall isn't synchronized, even if it was, what's correct behavior? Am I wrong in understanding this?Yes. The compiler will only reorder/run in parallel/optimize if it is safe (not changing execution semantics). Pure can be used to prove that certain optimizations are safe. If a pure function only takes const or immutable arguments, the compiler has more guarantees and can do more things. If a pure function takes mutable arguments, it can be used as a component of other pure functions (important!), but the kind of optimizations that can be performed directly on them are a lot more limited. 'Pure' means that the behavior of the function does not change between invocations with the same/equivalent arguments. This can include mutating actions on the arguments, if those are typed mutable.
Feb 04 2012
Probably the restriction was lifted after TDPL was out.Yes. The compiler will only reorder/run in parallel/optimize if it is safe (not changing execution semantics). Pure can be used to prove that certain optimizations are safe. If a pure function only takes const or immutable arguments, the compiler has more guarantees and can do more things. If a pure function takes mutable arguments, it can be used as a component of other pure functions (important!), but the kind of optimizations that can be performed directly on them are a lot more limited. 'Pure' means that the behavior of the function does not change between invocations with the same/equivalent arguments. This can include mutating actions on the arguments, if those are typed mutable.Even if you changed the signature of the pure function to 'pure int squaredPlus(immutable int);' you'd have the same problem; Because the int argument it receives is a copy so it won't matter if it was mutable or not. (If it were an object, then it would be more enforced). I'll refer to the language specs to see if I can find an answer on this, but it feels wrong allowing access to 'this' on mutable data; I thought it could only mutate it's own data in regards to local variables and arguments it owned.
Feb 04 2012
On 02/05/2012 12:15 AM, Era Scarecrow wrote:the signature I meant looks like pure int squaredPlus(int)immutable;Probably the restriction was lifted after TDPL was out.Yes. The compiler will only reorder/run in parallel/optimize if it is safe (not changing execution semantics). Pure can be used to prove that certain optimizations are safe. If a pure function only takes const or immutable arguments, the compiler has more guarantees and can do more things. If a pure function takes mutable arguments, it can be used as a component of other pure functions (important!), but the kind of optimizations that can be performed directly on them are a lot more limited. 'Pure' means that the behavior of the function does not change between invocations with the same/equivalent arguments. This can include mutating actions on the arguments, if those are typed mutable.Even if you changed the signature of the pure function to 'pure int squaredPlus(immutable int);' you'd have the same problem; Because the int argument it receives is a copy so it won't matter if it was mutable or not. (If it were an object, then it would be more enforced). I'll refer to the language specs to see if I can find an answer on this, but it feels wrong allowing access to 'this' on mutable data; I thought it could only mutate it's own data in regards to local variables and arguments it owned.
Feb 04 2012
the signature I meant looks like pure int squaredPlus(int)immutable;Which then the only way you could call it, was if the object itself was immutable, which is definitely safe (I think). Hmmm...
Feb 04 2012
On 02/05/2012 01:20 AM, Era Scarecrow wrote:Alternatively you can use pure int squaredPlus(int)const;, of course.the signature I meant looks like pure int squaredPlus(int)immutable;Which then the only way you could call it, was if the object itself was immutable, which is definitely safe (I think). Hmmm...
Feb 04 2012
On 2/4/2012 2:04 PM, Era Scarecrow wrote:[...] struct X { int i; pure int squaredPlus(int x) { return x*x + i } alias squaredPlus sqp; } X st(15); writeln(st.sqp(0)); //15 int i1 = st.sqp(10); st.i++; int i2 = st.sqp(10); st.i++; int i3 = st.sqp(10); st.i++; int i4 = st.sqp(10); st.i++;You can rewrite these like so: int i1 = sqp(st, 10); st.i++; int i2 = sqp(st, 10); st.i++; int i3 = sqp(st, 10); st.i++; int i4 = sqp(st, 10); At this point it becomes a little more obvious that these cannot be reordered because their arguments have a shared dependency, just like the following cannot be reordered: int i = 15; int i1 = i * i; ++i; int i2 = i * i; ++i; int i3 = i * i; ++i; int i4 = i * i; I hope we all agree that opMult(int, int) is pure, and that is both safe to reorder its execution with non-dependent args, and unsafe to do so here.assert(i1 == 100); //pass/fail? [...]Fail. It should be i1 == 115. ;) Dave
Jan 02 2014
On 2/4/2012 12:45 PM, Timon Gehr wrote:[...] Pure does not imply const in D. [...]I think this is a language defect: struct Foo { int computed() pure { return x * y; } int wrapper() const { return computed() + 5; } int x; int y; } void main() { } src\Bug2.d(4): Error: mutable method Bug2.Foo.computed is not callable using a const object Surprisingly, this is legal, and "fixes" the issue: int computed() const pure { return x * y; } I say this is a bug because of what a "non-const pure" method would be: a method which could somehow modify 'this', or its members. I hope we all agree that such a method is not, in fact, pure. Let's try, just to make sure: int computed() pure { return ++x * y; } Oh noes...this is allowed!!! Surely this is wrong. Suppose we rewrote the method like so: int computed(ref int x, int y) pure { return ++x * y; } Would anyone say this is legit? Dave
Jan 02 2014
On Thu, Jan 02, 2014 at 04:31:24PM -0800, David Held wrote:
[...]
I think this is a language defect:
struct Foo
{
int computed() pure { return x * y; }
int wrapper() const { return computed() + 5; }
int x;
int y;
}
void main()
{
}
src\Bug2.d(4): Error: mutable method Bug2.Foo.computed is not
callable using a const object
Surprisingly, this is legal, and "fixes" the issue:
int computed() const pure { return x * y; }
I say this is a bug because of what a "non-const pure" method would
be: a method which could somehow modify 'this', or its members. I
hope we all agree that such a method is not, in fact, pure. Let's
try, just to make sure:
int computed() pure { return ++x * y; }
Oh noes...this is allowed!!! Surely this is wrong. Suppose we
rewrote the method like so:
int computed(ref int x, int y) pure { return ++x * y; }
Would anyone say this is legit?
[...]
You're confused because D's purity system is not quite the same as the
purity in functional languages. In D, there's the notion of "strong
purity" vs. "weak purity":
- Strong purity is what most people would understand as "pure" in the
functional language sense. In D, this is the purity you get when a
function is (1) pure, and (2) its arguments have no mutable
indirections.
- D, however, extends the notion of purity to what is called "weak
purity", which permits mutation of external data, but with the
restriction that this mutation can only take place through references
passed in as arguments to the function.
This is why you need to write "const pure" when you want strong purity,
because under the definition of weak purity, the function is allowed to
modify data via 'this'.
What's the point of weak purity, you may ask? The basic idea is this:
since D is an imperative language, it is most natural to write
imperative style code containing mutation of local variables, etc., but
from a functional language's POV these are all impure operations and are
therefore not allowed in 'pure' code. So you'd have to write 'pure'
functions in a convoluted, unnatural style (i.e., in a functional style)
in order to conform to the definition of purity. However, this
restriction is actually superfluous if all of the side-effects caused by
the function is never visible to the outside world. Mutation of local
variables is allowed because nobody outside the function will see this
mutation. So, as far as the outside world is concerned, the function is
still pure, and the fact that it uses 'impure' constructs like mutation
of local variables is a mere implementation detail. So modification of
local state is permitted in pure functions.
So far so good. But what of weak purity? The motivation behind weak
purity comes from the observation that if some helper function, let's
call it helper(), can only mutate data via its arguments, then it is
legal to call it from a strongly-pure function, because since the
strongly-pure function has no outside-observable side-effects, any data
it is allowed to mutate can only be local state unobservable to the
outside world. This in turn means that it is impossible for the
strongly-pure function to pass a mutable reference to any global state
to helper(), and therefore, whatever helper() does can only affect state
local to the strongly-pure function. As a result, none of helper()'s
side-effects (when called from a strongly-pure function) will be visible
to the outside world either.
This expanded scope of allowable code in a strongly-pure function is
desirable, because it can simplify implementation in many cases.
Duplicated code can be avoided (no need to rewrite something just
because a pure function needs to call it but it just so happens to not
be strongly pure), and you can create class instances inside a
strongly-pure function and mutate it by calling its methods -- as long
as the methods are subject to the restriction that they only ever mutate
state reachable via their arguments, and nothing else. As long as the
class instance remains strictly local to the strongly-pure function, the
outside world wouldn't know any better (nor care) -- the function is
still strongly pure as far as it is concerned. Thus, we can expand the
scope of what can be considered pure in D, while still enjoying the
benefits of purity (no (observable) side-effects, cacheability, etc.).
So in view of this, D's 'pure' keyword has come to mean 'weakly pure',
that is, modification of data is allowed via indirections passed in as
function arguments (the 'this' pointer is considered an argument), but
direct modification of global state is strictly prohibited. Finally, a
(weakly) pure function is strongly-pure when its arguments contain no
mutable indirections, because then, together with the weakly-pure
restriction, this implies that the function cannot modify any external
state at all, since the only permitted avenue of doing so -- via
function arguments -- contains no mutable indirections, so the function
is unable to reach any outside state.
This is the reason for Timon's response that you need to write 'const
pure' when you mean "strongly-pure"; "pure" by itself means
"weakly-pure" because the 'this' pointer is mutable by default.
T
--
Маленькие детки - маленькие бедки.
Jan 02 2014









"Daniel Murphy" <yebblies nospamgmail.com> 