www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Generic code: autoconst, autopure, autonothrow

reply dsimcha <dsimcha yahoo.com> writes:
An issue that comes up very frequently when trying to use const, pure or
nothrow in generic code is lack of knowledge of whether the functions you're
calling are const/pure/nothrow.  For example:

T abs(T num) pure nothrow {
    return (num < 0) ? -1 * num : num;
}

Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
can't practically be made pure.  A solution I propose is to allow the
annotations  autoconst,  autopure and  autonothrow for template functions.
These would mean "everything I do is const/pure/nothrow as long as all of the
functions I call are const/pure/nothrow".  As far as I can tell, this would be
reasonably implementable because the compiler always has the source code to
template functions, unlike non-template functions.

A pure function would be allowed to call an  autopure function instantiated
with a type that makes it pure, and similarly for const and nothrow.  Taking
the address of an instantiation of an  autopure function would return a pure
function pointer iff the instantiation was pure.  If an  autopure function
tried to do anything impure other than call an impure function, ideally the
result would not compile, since the function is falsely asserting that
everything it does itself is pure.  This is not an absolute requirement, though.

I believe that this would massively simplify correctly using
const/pure/nothrow in generic code, which currently is near impossible.  Does
this sound like it could feasibly be implemented and would work well?
Aug 28 2010
next sibling parent Jonathan M Davis <jmdavisprog gmail.com> writes:
On Saturday 28 August 2010 18:29:02 dsimcha wrote:
 An issue that comes up very frequently when trying to use const, pure or
 nothrow in generic code is lack of knowledge of whether the functions
 you're calling are const/pure/nothrow.  For example:
 
 T abs(T num) pure nothrow {
     return (num < 0) ? -1 * num : num;
 }
 
 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure
 and can't practically be made pure.  A solution I propose is to allow the
 annotations  autoconst,  autopure and  autonothrow for template functions.
 These would mean "everything I do is const/pure/nothrow as long as all of
 the functions I call are const/pure/nothrow".  As far as I can tell, this
 would be reasonably implementable because the compiler always has the
 source code to template functions, unlike non-template functions.
 
 A pure function would be allowed to call an  autopure function instantiated
 with a type that makes it pure, and similarly for const and nothrow. 
 Taking the address of an instantiation of an  autopure function would
 return a pure function pointer iff the instantiation was pure.  If an
  autopure function tried to do anything impure other than call an impure
 function, ideally the result would not compile, since the function is
 falsely asserting that everything it does itself is pure.  This is not an
 absolute requirement, though.
 
 I believe that this would massively simplify correctly using
 const/pure/nothrow in generic code, which currently is near impossible. 
 Does this sound like it could feasibly be implemented and would work well?
It certainly sounds good to me, though I'm probably not well enough versed on all the implications of const, nothrow, and pure to know exactly what effects this will have. Still, from where I sit, it sounds like a good idea. - Jonathan M Davis
Aug 28 2010
prev sibling next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 08/28/2010 08:29 PM, dsimcha wrote:
 An issue that comes up very frequently when trying to use const, pure or
 nothrow in generic code is lack of knowledge of whether the functions you're
 calling are const/pure/nothrow.  For example:

 T abs(T num) pure nothrow {
      return (num<  0) ? -1 * num : num;
 }

 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
 can't practically be made pure.  A solution I propose is to allow the
 annotations  autoconst,  autopure and  autonothrow for template functions.
 These would mean "everything I do is const/pure/nothrow as long as all of the
 functions I call are const/pure/nothrow".  As far as I can tell, this would be
 reasonably implementable because the compiler always has the source code to
 template functions, unlike non-template functions.

 A pure function would be allowed to call an  autopure function instantiated
 with a type that makes it pure, and similarly for const and nothrow.  Taking
 the address of an instantiation of an  autopure function would return a pure
 function pointer iff the instantiation was pure.  If an  autopure function
 tried to do anything impure other than call an impure function, ideally the
 result would not compile, since the function is falsely asserting that
 everything it does itself is pure.  This is not an absolute requirement,
though.

 I believe that this would massively simplify correctly using
 const/pure/nothrow in generic code, which currently is near impossible.  Does
 this sound like it could feasibly be implemented and would work well?
Yah, with the growing interest in applying qualifiers at a larger scale (and making Phobos a good example of such) this is quite timely. I've been mulling myself over a similar proposal. What I had in mind is a bit more structured - it would allow selecting a type or an expression and assessing its constness/purity. That would be done just like .sizeof and .stringof work - by defining two more special members .constof, .pureof, .nothrowof. For example: T abs(T num) (-1 * num).pureof (-1 * num).nothrowof { return (num < 0) ? -1 * num : num; } The autoxxx stuff arguably makes this example and probably many others easier to write. What I fear in the case of autoxxx is that the compiler will be unable to decide cases in which there are cycles of autoxxx. Andrei
Aug 28 2010
parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Andrei Alexandrescu (SeeWebsiteForEmail erdani.org)'s article
 Yah, with the growing interest in applying qualifiers at a larger scale
 (and making Phobos a good example of such) this is quite timely. I've
 been mulling myself over a similar proposal.
 What I had in mind is a bit more structured - it would allow selecting a
 type or an expression and assessing its constness/purity. That would be
 done just like .sizeof and .stringof work - by defining two more special
 members .constof, .pureof, .nothrowof. For example:
 T abs(T num) (-1 * num).pureof (-1 * num).nothrowof {
      return (num < 0) ? -1 * num : num;
 }
This may work well in Phobos because it's the standard library, so a lot of effort could reasonably be put into making stuff like this right. For run of the mill generic code, though, this just seems too verbose. If this is what I had to write, about the only time I would use it is when hacking Phobos. I probably would even be too lazy to use it for my more specialized libraries like dstats. Also, how does it scale to the case where you need to evaluate the purity of multiple expressions to decide how to qualify a single function?
 The  autoxxx stuff arguably makes this example and probably many others
 easier to write. What I fear in the case of  autoxxx is that the
 compiler will be unable to decide cases in which there are cycles of
  autoxxx.
Good point, though in practice I can't think of any time I've written or seen mutually recursive templated code. I guess the cycle-breaking logic could just treat the mutually recursive functions as effectively being one function. If all would compile when qualified with x, then all are x. Otherwise, none are x. Is it more complex than this for some reason I'm not thinking of? BTW, now that I think of it, I guess these proposals also apply to safe.
Aug 28 2010
prev sibling next sibling parent reply Rainer Deyke <rainerd eldwood.com> writes:
On 8/28/2010 19:29, dsimcha wrote:
 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
 can't practically be made pure.  A solution I propose is to allow the
 annotations  autoconst,  autopure and  autonothrow for template functions.
 These would mean "everything I do is const/pure/nothrow as long as all of the
 functions I call are const/pure/nothrow".  As far as I can tell, this would be
 reasonably implementable because the compiler always has the source code to
 template functions, unlike non-template functions.
On one hand, this addresses a real need. On the other hand, D is already has a serious case featuritis. Is there any real reason why we can't apply these modifiers automatically to all functions? (And by "real" I don't mean "it would be hard to do" or "it is incompatible with the archaic compilation model chosen by one D implementation".) Failing that, are the arguments for the inclusion of pure/nothrow/const really strong enough to justify all this extra cruft in the language? -- Rainer Deyke - rainerd eldwood.com
Aug 28 2010
next sibling parent reply dsimcha <dsimcha yahoo.com> writes:
== Quote from Rainer Deyke (rainerd eldwood.com)'s article
 On 8/28/2010 19:29, dsimcha wrote:
 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
 can't practically be made pure.  A solution I propose is to allow the
 annotations  autoconst,  autopure and  autonothrow for template functions.
 These would mean "everything I do is const/pure/nothrow as long as all of the
 functions I call are const/pure/nothrow".  As far as I can tell, this would be
 reasonably implementable because the compiler always has the source code to
 template functions, unlike non-template functions.
On one hand, this addresses a real need. On the other hand, D is already has a serious case featuritis. Is there any real reason why we can't apply these modifiers automatically to all functions? (And by "real" I don't mean "it would be hard to do" or "it is incompatible with the archaic compilation model chosen by one D implementation".) Failing that, are the arguments for the inclusion of pure/nothrow/const really strong enough to justify all this extra cruft in the language?
Two reasons: 1. Unless the function is a template, the compiler isn't guaranteed to have the source available. What if it's a binary-only library? 2. The modifiers are part of a contract and part of the public API. What if some function just happens to be pure now, but you consider that an implementation detail, not part of its specification? Client code may rely on this, not realizing it's an implementation detail. Then, when you make the function impure, your client code will break.
Aug 28 2010
parent reply Rainer Deyke <rainerd eldwood.com> writes:
On 8/28/2010 22:33, dsimcha wrote:
 Is there any real reason why we can't apply these modifiers
 automatically to all functions?  (And by "real" I don't mean "it would
 be hard to do" or "it is incompatible with the archaic compilation model
 chosen by one D implementation".)
Two reasons: 1. Unless the function is a template, the compiler isn't guaranteed to have the source available. What if it's a binary-only library?
"It is incompatible with the archaic compilation model chosen by one D implementation." This special treatment of templates has got to end.
 2.  The modifiers are part of a contract and part of the public API.  What if
some
 function just happens to be pure now, but you consider that an implementation
 detail, not part of its specification?  Client code may rely on this, not
 realizing it's an implementation detail.  Then, when you make the function
impure,
 your client code will break.
That may or may not be a compelling argument against always auto-detecting pure. It seems stronger as an argument against having pure as a language feature at all. (How can you know ahead of time that a logically pure function will remain physically pure?) -- Rainer Deyke - rainerd eldwood.com
Aug 28 2010
next sibling parent "Nick Sabalausky" <a a.a> writes:
"Rainer Deyke" <rainerd eldwood.com> wrote in message 
news:i5cogl$31j9$1 digitalmars.com...
 On 8/28/2010 22:33, dsimcha wrote:
 Is there any real reason why we can't apply these modifiers
 automatically to all functions?  (And by "real" I don't mean "it would
 be hard to do" or "it is incompatible with the archaic compilation model
 chosen by one D implementation".)
Two reasons: 1. Unless the function is a template, the compiler isn't guaranteed to have the source available. What if it's a binary-only library?
"It is incompatible with the archaic compilation model chosen by one D implementation." This special treatment of templates has got to end.
I assume you mean "This special treatment of binary-only libraries has got to end"? If so, I agree.
Aug 28 2010
prev sibling next sibling parent reply Jonathan M Davis <jmdavisprog gmail.com> writes:
On Saturday 28 August 2010 21:42:49 Rainer Deyke wrote:
 On 8/28/2010 22:33, dsimcha wrote:
 Is there any real reason why we can't apply these modifiers
 automatically to all functions?  (And by "real" I don't mean "it would
 be hard to do" or "it is incompatible with the archaic compilation model
 chosen by one D implementation".)
Two reasons: 1. Unless the function is a template, the compiler isn't guaranteed to have the source available. What if it's a binary-only library?
"It is incompatible with the archaic compilation model chosen by one D implementation." This special treatment of templates has got to end.
Templates are instantiated when you use them. They can't work any other way. Normal functions are instantiated where they are declared. Unless you want to try and make it so that _all_ functions are instantiated where they are used (which IMHO seems like a really _bad_ idea), templates are and must be treated differently. There's no way around it. It was tried with exported templates in C++ and that idea was shown to be horribly broken. You just can't do it. - Jonathan M Davis
Aug 28 2010
parent Rainer Deyke <rainerd eldwood.com> writes:
On 8/29/2010 00:32, Jonathan M Davis wrote:
 Templates are instantiated when you use them. They can't work any other way. 
 Normal functions are instantiated where they are declared. Unless you want to 
 try and make it so that _all_ functions are instantiated where they are used 
 (which IMHO seems like a really _bad_ idea), templates are and must be treated 
 differently.
Why would that be a bad idea? You gain: - Consistency. - The ability to treat all libraries as "header-only", with no separate compilation. - Because there is no separate compilation, templated virtual functions. (Funny how that works out.) - Better global optimizations. You lose: - Binary libraries. (Which barely work anyway, because most of my functions are already templated.) - Potentially some (or even a lot of) compilation speed, and potentially not. Separate compilation introduces its own slow-downs. -- Rainer Deyke - rainerd eldwood.com
Aug 29 2010
prev sibling next sibling parent Pelle <pelle.mansson gmail.com> writes:
On 08/29/2010 06:42 AM, Rainer Deyke wrote:
 On 8/28/2010 22:33, dsimcha wrote:
 2.  The modifiers are part of a contract and part of the public API.  What if
some
 function just happens to be pure now, but you consider that an implementation
 detail, not part of its specification?  Client code may rely on this, not
 realizing it's an implementation detail.  Then, when you make the function
impure,
 your client code will break.
That may or may not be a compelling argument against always auto-detecting pure. It seems stronger as an argument against having pure as a language feature at all. (How can you know ahead of time that a logically pure function will remain physically pure?)
I'm not sure I understand what you mean, but if you put pure on a function in your library, you're saying to whoever using it that it is and probably will always be pure. Also, if you put autopure on it implies that if the template input is pure in the right places, the function will be pure. If we remove pure, or make everything always autopure, this benefit disappears. Which is why I don't know if I get your point.
Aug 29 2010
prev sibling parent reply "Robert Jacques" <sandford jhu.edu> writes:
On Sun, 29 Aug 2010 00:42:49 -0400, Rainer Deyke <rainerd eldwood.com>  
wrote:

 On 8/28/2010 22:33, dsimcha wrote:
 Is there any real reason why we can't apply these modifiers
 automatically to all functions?  (And by "real" I don't mean "it would
 be hard to do" or "it is incompatible with the archaic compilation  
 model
 chosen by one D implementation".)
Two reasons: 1. Unless the function is a template, the compiler isn't guaranteed to have the source available. What if it's a binary-only library?
"It is incompatible with the archaic compilation model chosen by one D implementation." This special treatment of templates has got to end.
So... dynamic libraries (DLL's, etc.) are an archaic compilation model? Might you be able to suggest an newer alternative? (that doesn't involve a JIT, of course)
Aug 29 2010
next sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Robert Jacques (sandford jhu.edu)'s article
 On Sun, 29 Aug 2010 00:42:49 -0400, Rainer Deyke <rainerd eldwood.com>
 wrote:
 On 8/28/2010 22:33, dsimcha wrote:
 Is there any real reason why we can't apply these modifiers
 automatically to all functions?  (And by "real" I don't mean "it would
 be hard to do" or "it is incompatible with the archaic compilation
 model
 chosen by one D implementation".)
Two reasons: 1. Unless the function is a template, the compiler isn't guaranteed to have the source available. What if it's a binary-only library?
"It is incompatible with the archaic compilation model chosen by one D implementation." This special treatment of templates has got to end.
So... dynamic libraries (DLL's, etc.) are an archaic compilation model? Might you be able to suggest an newer alternative? (that doesn't involve a JIT, of course)
Sometimes I wonder if, once higher priority issues have been taken care of and D is mainstream enough to support multiple dialects, we should create a D dialect for standalone programs. From a language specification point of view this would be basically a superset of D, but would disallow separate compilation, i.e. all non-extern(C) code for the entire project must be passed to the compiler in a single invocation. This would allow: 1. Using templates to add virtual functions to classes. 2. Compile-time introspection to get all descendants of a class. 3. Better whole-program optimization, because no ABI for this dialect of D would have to be specified, so the compiler could just do whatever is most efficient.
Aug 29 2010
prev sibling parent Rainer Deyke <rainerd eldwood.com> writes:
On 8/29/2010 08:44, Robert Jacques wrote:
 So... dynamic libraries (DLL's, etc.) are an archaic compilation model?
 Might you be able to suggest an newer alternative? (that doesn't involve
 a JIT, of course)
Not necessarily, but it /is/ a special case. Dlls are best treated as separate programs communicating through C function calls. C function calls are used to achieve language neutrality. C function calls don't support pure and D-const, so the purity and constness of functions at the dll boundary is a moot point. -- Rainer Deyke - rainerd eldwood.com
Aug 29 2010
prev sibling parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Rainer Deyke (rainerd eldwood.com)'s article
 On one hand, this addresses a real need.  On the other hand, D is
 already has a serious case featuritis.
With regard to featuritis, I see your point, but IMHO without something like this pure and nothrow are the worst kind of extra features: Dead weight features. If they clash so severely with generic programming, then to a large extent they're useless. Very few people will use them if it means that they can't use D's metaprogramming capabilities or half of Phobos in the same code.
Aug 28 2010
prev sibling next sibling parent reply Brad Roberts <braddr puremagic.com> writes:
On 8/28/2010 6:29 PM, dsimcha wrote:
 An issue that comes up very frequently when trying to use const, pure or
 nothrow in generic code is lack of knowledge of whether the functions you're
 calling are const/pure/nothrow.  For example:
 
 T abs(T num) pure nothrow {
     return (num < 0) ? -1 * num : num;
 }
 
 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
 can't practically be made pure.  A solution I propose is to allow the
 annotations  autoconst,  autopure and  autonothrow for template functions.
 These would mean "everything I do is const/pure/nothrow as long as all of the
 functions I call are const/pure/nothrow".  As far as I can tell, this would be
 reasonably implementable because the compiler always has the source code to
 template functions, unlike non-template functions.
 
 A pure function would be allowed to call an  autopure function instantiated
 with a type that makes it pure, and similarly for const and nothrow.  Taking
 the address of an instantiation of an  autopure function would return a pure
 function pointer iff the instantiation was pure.  If an  autopure function
 tried to do anything impure other than call an impure function, ideally the
 result would not compile, since the function is falsely asserting that
 everything it does itself is pure.  This is not an absolute requirement,
though.
 
 I believe that this would massively simplify correctly using
 const/pure/nothrow in generic code, which currently is near impossible.  Does
 this sound like it could feasibly be implemented and would work well?
This really feels like a work-around rather than addressing the underlying problem. These annotations are defining the contract for the function. The fact that it's generic just means it's being flexible with the types, but that flexibility shouldn't extend to allowing the contract to be violated. Abs _should_ be pure. If the type being passed to it is incapable performing the algorithm purely, then the type isn't valid to be used with abs. Trying to say that abs should be pure sometimes and not others.. why bother having the pure requirement at all? Maybe it's a bad example, but let's not loose site of the reason pure exists. It's not just for the side effects, it's for the contract it defines as well. Later, Brad
Aug 28 2010
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Brad Roberts:
 Abs _should_ be pure.  If the type being passed to it is incapable performing
 the algorithm purely, then the type isn't valid to be used with abs.
I agree. On the other hand if you use multiprecision GNU numbers, your large numbers aren't immutable, you are able to change their sign in-place. Is it an abs() or something different like inplaceAbs()? Bye, bearophile
Aug 29 2010
parent reply Justin Johansson <no spam.com> writes:
On 29/08/10 21:36, bearophile wrote:
 ... if you use multiprecision GNU numbers, your large numbers aren't
immutable, you are able to change their sign in-place.
Is the reason for this due to some optimization strategy to avoid copying and changing the sign on the copy?
Aug 29 2010
parent bearophile <bearophileHUGS lycos.com> writes:
Justin Johansson:
 Is the reason for this due to some optimization strategy to
 avoid copying and changing the sign on the copy?
If each of your numbers need 500 MB of RAM, copying a number just to change its sign, and then free the original number is a significant waste of time. So it's an optimization offered by GMP. Bye, bearophile
Aug 29 2010
prev sibling parent reply =?iso-8859-2?B?VG9tZWsgU293afFza2k=?= <just ask.me> writes:
Dnia 29-08-2010 o 06:57:17 Brad Roberts <braddr puremagic.com> napisa=B3=
(a):

 On 8/28/2010 6:29 PM, dsimcha wrote:
 An issue that comes up very frequently when trying to use const, pure=
or
 nothrow in generic code is lack of knowledge of whether the functions=
=
 you're
 calling are const/pure/nothrow.  For example:

 T abs(T num) pure nothrow {
     return (num < 0) ? -1 * num : num;
 }

 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't=
=
 pure and
 can't practically be made pure.  A solution I propose is to allow the=
 annotations  autoconst,  autopure and  autonothrow for template  =
 functions.
 These would mean "everything I do is const/pure/nothrow as long as al=
l =
 of the
 functions I call are const/pure/nothrow".  As far as I can tell, this=
=
 would be
 reasonably implementable because the compiler always has the source  =
 code to
 template functions, unlike non-template functions.

 A pure function would be allowed to call an  autopure function  =
 instantiated
 with a type that makes it pure, and similarly for const and nothrow. =
=
 Taking
 the address of an instantiation of an  autopure function would return=
a =
 pure
 function pointer iff the instantiation was pure.  If an  autopure  =
 function
 tried to do anything impure other than call an impure function, ideal=
ly =
 the
 result would not compile, since the function is falsely asserting tha=
t
 everything it does itself is pure.  This is not an absolute  =
 requirement, though.

 I believe that this would massively simplify correctly using
 const/pure/nothrow in generic code, which currently is near  =
 impossible.  Does
 this sound like it could feasibly be implemented and would work well?=
 This really feels like a work-around rather than addressing the  =
 underlying
 problem.  These annotations are defining the contract for the function=
. =
 The
 fact that it's generic just means it's being flexible with the types, =
=
 but that
 flexibility shouldn't extend to allowing the contract to be violated.

 Abs _should_ be pure.  If the type being passed to it is incapable  =
 performing
 the algorithm purely, then the type isn't valid to be used with abs.

 Trying to say that abs should be pure sometimes and not others.. why  =
 bother
 having the pure requirement at all?

 Maybe it's a bad example, but let's not loose site of the reason pure =
=
 exists.
 It's not just for the side effects, it's for the contract it defines a=
s =
 well.
How about reduce!fun(range)? It's pure/nothrow when fun is pure/nothrow.= = Plenty of std.algorithm would benefit. BTW, small suggestion: autopure -> auto(pure) Tomek
Aug 30 2010
parent reply =?iso-8859-2?B?VG9tZWsgU293afFza2k=?= <just ask.me> writes:
Dnia 30-08-2010 o 21:10:10 Tomek Sowi=F1ski <just ask.me> napisa=B3(a):

 How about reduce!fun(range)? It's pure/nothrow when fun is pure/nothro=
w. =
 Plenty of std.algorithm would benefit.
Eh, nevermind. popFront() must mutate the range so it can't be pure. Nee= d = to get some sleep...
Aug 30 2010
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/30/10 14:16 PDT, Tomek Sowiński wrote:
 Dnia 30-08-2010 o 21:10:10 Tomek Sowiński <just ask.me> napisał(a):

 How about reduce!fun(range)? It's pure/nothrow when fun is
 pure/nothrow. Plenty of std.algorithm would benefit.
Eh, nevermind. popFront() must mutate the range so it can't be pure. Need to get some sleep...
It can still be nothrow depending on input, which makes a solid point. Arguments against qualifier/attribute propagation based on sheer semantics ("it's abs so it must be pure") break badly in the face of higher-order functions. It's pretty clear we need an attribute propagation mechanism if we want e.g. to make Phobos const-aware. Andrei
Aug 30 2010
parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
2010/8/30 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org>

 On 8/30/10 14:16 PDT, Tomek Sowi=F1ski wrote:

 Dnia 30-08-2010 o 21:10:10 Tomek Sowi=F1ski <just ask.me> napisa=B3(a):

  How about reduce!fun(range)? It's pure/nothrow when fun is
 pure/nothrow. Plenty of std.algorithm would benefit.
Eh, nevermind. popFront() must mutate the range so it can't be pure. Need to get some sleep...
It can still be nothrow depending on input, which makes a solid point. Arguments against qualifier/attribute propagation based on sheer semantic=
s
 ("it's abs so it must be pure") break badly in the face of higher-order
 functions.

 It's pretty clear we need an attribute propagation mechanism if we want
 e.g. to make Phobos const-aware.
Could something like this be a possible building block for pure? import std.stdio, std.bigint; /** Test whether expression expr is pure for type Type. */ template isPure(Type, string expr) { enum isPure =3D isPureImpl!(Type, expr); } bool isPureImpl(Type, string expr)() { mixin("auto pureTester(T)(T a) pure { return " ~ expr ~ ";}"); return is(typeof( pureTester(Type.init) )); } int foo(int i) { return i;} // not pure int bar(int i) pure { return i;}// pure void test(T)(T num) if (isPure!(T, "-1*a")) // test that with "-1*a", "-1*foo(a)" and "-1*bar(a)" { writeln("Yes, pure expr for type " ~ T.stringof); } void main() { test(1); auto b =3D BigInt("1"); // test(b); // is -1*b a pure expr for BigInts? } test(1) will compile for expression -1*a or just "a" test(BigInt("1")) will not compile with expression -1*a, because it's not pure for BigInts. It will compile with just "a" as constraint expression. I also tried to instantiate test(1) with -1*bar(a) and -1*foo(a) as constraints. It passes for -1*bar(a), because bar is pure, but not with foo(a). Maybe then it's possible to create a pure version of a function if this passes the test. I'm not sure, it's late in there. Also, this could be done also for nothrow. I didn't think it through for const. Philippe
Aug 30 2010
prev sibling parent =?iso-8859-2?B?VG9tZWsgU293afFza2k=?= <just ask.me> writes:
Dnia 30-08-2010 o 21:16:15 Tomek Sowi=F1ski <just ask.me> napisa=B3(a):

 Dnia 30-08-2010 o 21:10:10 Tomek Sowi=F1ski <just ask.me> napisa=B3(a)=
:
 How about reduce!fun(range)? It's pure/nothrow when fun is  =
 pure/nothrow. Plenty of std.algorithm would benefit.
Eh, nevermind. popFront() must mutate the range so it can't be pure. =
 Need to get some sleep...
But wait! It can be auto(nothrow). So my sleepy-head argument didn't suc= k = after all:) And I do like the auto(qual) syntax. BTW, isn't it surprising? Think of sum of squares, I mean reduce!"a + b = * = b"(range) -- it should be pure, it's natural. Come to think of it, if pu= re = functions required arguments to be tail immutable**, not from-head = immutable as it is now, popFront() being also tail immutable would = advance the range and it would just work... Damn, now I can't sleep. ** tail immutable doesn't exist in the language. It was proposed in the= = great const debate not too long ago. Tomek
Aug 30 2010
prev sibling next sibling parent reply Peter Alexander <peter.alexander.au gmail.com> writes:
On 29/08/10 2:29 AM, dsimcha wrote:
 An issue that comes up very frequently when trying to use const, pure or
 nothrow in generic code is lack of knowledge of whether the functions you're
 calling are const/pure/nothrow.  For example:

 T abs(T num) pure nothrow {
      return (num<  0) ? -1 * num : num;
 }

 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
 can't practically be made pure.
I think the problem here is *not* that it's difficult to tell whether a function can be pure, but the fact that BigInt's binary product should be pure (because it is, in the mathematical sense). This is just the whole const without mutable thing all over again i.e. there exist logically const member functions that change the state of their members (caching is the canonical example of this). This is why mutable exists. Similarly, there exist logically pure function that change the global state, or allocate memory (such as BigInt's op* here). There needs to be some way around that. If there cannot be any way around it then pure should be dropped altogether because the language simply cannot support it.
Aug 29 2010
parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Peter Alexander (peter.alexander.au gmail.com)'s article
 On 29/08/10 2:29 AM, dsimcha wrote:
 An issue that comes up very frequently when trying to use const, pure or
 nothrow in generic code is lack of knowledge of whether the functions you're
 calling are const/pure/nothrow.  For example:

 T abs(T num) pure nothrow {
      return (num<  0) ? -1 * num : num;
 }

 Looks pretty good.  Won't work with BigInt because opBinary!"*" isn't pure and
 can't practically be made pure.
I think the problem here is *not* that it's difficult to tell whether a function can be pure, but the fact that BigInt's binary product should be pure (because it is, in the mathematical sense). This is just the whole const without mutable thing all over again i.e. there exist logically const member functions that change the state of their members (caching is the canonical example of this). This is why mutable exists. Similarly, there exist logically pure function that change the global state, or allocate memory (such as BigInt's op* here). There needs to be some way around that. If there cannot be any way around it then pure should be dropped altogether because the language simply cannot support it.
Part of the point of purity is to provide guarantees useful for multithreading. For these cases logical purity isn't what's important. Physical purity is. I agree that D's purity system is a bit conservative and should ideally be made less conservative, but it doesn't change the fact physical purity is what matters to threading.
Aug 29 2010
prev sibling next sibling parent reply "Simen kjaeraas" <simen.kjaras gmail.com> writes:
dsimcha <dsimcha yahoo.com> wrote:

[Interesting stuff]

This problem is much bigger that  auto<foo>. When we get a new  ribute
that affects code in a similar way, are we to add  autofnord  autogrok
 autogazillions to the language as well? If anything, this system should
be made extensible. I suggest the syntax

      auto[ <value> ]

where <value> is a comma-separated list of  ributes.

This also leads to another (somewhat unrelated, I admit) idea of mine.
Occasionally, you want attributes to be determined by template
parameters. For this, I suggest the following:

      attribute( condition )[ <value> ]

Where <condition> follows the system of template constraints. <value> is
as above, a comma-separated list of  ributes. If <condition> evaluates
to true, the  ributes in <value> are applied to whatever follows.

-- 
Simen
Aug 29 2010
next sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Couldn't you reuse the version statement for that? At least for a
group of attribute-related functions this might work (but I haven't
tried):

version(pure)
{
     pure
    void foo() {...}

     pure
    void bar() {...}
}
else
{
    void foo() {...}
    void bar() {...}
}

For this, I suggest the following:
 =A0 =A0 attribute( condition )[ <value> ]

 Where <condition> follows the system of template constraints. <value> is
 as above, a comma-separated list of  ributes. If <condition> evaluates
 to true, the  ributes in <value> are applied to whatever follows.

 --
 Simen
Aug 29 2010
parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
Andrej Mitrovic <andrej.mitrovich gmail.com> wrote:

 Couldn't you reuse the version statement for that? At least for a
 group of attribute-related functions this might work
Static if, at least. Still, that is the road to extreme code bloat. Imagine having to write those for all combinations of pure, nothrow, const/immutable, override, protected/public/package, final, and synchronized (arguably a constructed situation, but still). -- Simen
Aug 29 2010
prev sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Simen kjaeraas:
 I suggest the syntax
       auto[ <value> ]
 where <value> is a comma-separated list of  ributes.
That's bad. I prefer a more flexible semantics, so auto becomes one case of user-defined scoped properties. Bye, bearophile
Aug 29 2010
parent "Simen kjaeraas" <simen.kjaras gmail.com> writes:
bearophile <bearophileHUGS lycos.com> wrote:

 Simen kjaeraas:
 I suggest the syntax
       auto[ <value> ]
 where <value> is a comma-separated list of  ributes.
That's bad. I prefer a more flexible semantics, so auto becomes one case of user-defined scoped properties.
Please do elucidate. Does the second part of my post perhaps help define something akin to what you envision? -- Simen
Aug 29 2010
prev sibling next sibling parent bearophile <bearophileHUGS lycos.com> writes:
Simen kjaeraas:
 Please do elucidate.
I am not able to design user-defined attributes in a short time all alone :-) I'd like to be able to define scoped attributes in normal D code, so you can extend a bit the D type system in user-code, using static introspection (using __traits and other things). ("scoped" means that you are able to use them only in the scope where you have defined them, and you are able to import them).
 Does the second part of my post perhaps help define
 something akin to what you envision?
It's a starting point, but in my opinion quite more design work is needed. First of all I'd like to define one attribute, for example using alias: alias attribute(SomeCondition!A)[A...] autopure; You may even use a template-like syntax, but it's a bit overkill: attribute autopure(A...) { You are surely able to do better. Anyway, I think all this is for D3. For D2 I think it's better to fix the features already present or planned before creating new ones. The bug reports I've suggested to Don (among my ones) in my opinion comes before truly new features. Bye, bearophile
Aug 29 2010
prev sibling parent reply Pillsy <pillsbury gmail.com> writes:
Andrei Alexandrescu Wrote:

 On 08/28/2010 08:29 PM, dsimcha wrote:
 An issue that comes up very frequently when trying to use 
 const, pure or nothrow in generic code is lack of knowledge 
 of whether the functions you're calling are 
 const/pure/nothrow.  For example:
 T abs(T num) pure nothrow {
      return (num<  0) ? -1 * num : num;
 }
[...]
 A solution I propose is to allow the annotations  autoconst,
  autopure and  autonothrow for template functions.
[...]
 Does this sound like it could feasibly be implemented and
 would work well?
 Yah, with the growing interest in applying qualifiers at a larger scale 
 (and making Phobos a good example of such) this is quite timely. 
 I've been mulling myself over a similar proposal.
 What I had in mind is a bit more structured - it would allow 
 selecting a type or an expression and assessing its 
 constness/purity. That would be done just like .sizeof and 
 .stringof work - by defining two more special members
 .constof, .pureof, .nothrowof. For example:
 T abs(T num) (-1 * num).pureof (-1 * num).nothrowof {
      return (num < 0) ? -1 * num : num;
 }
Could something along the lines of a .qualof member work, for those times where you want to duplicate all relevant qualifiers? That seems likely to be the general case, and could considerably reduce the verbosity involved. T abs(T num) (-1 * num).qualof { return (num < 0) ? -1 * num : num; } is even shorter, and would have the possibility of also handling concerns of constness and sharedness and the like properly. Cheers, Pillsy
Aug 30 2010
parent dsimcha <dsimcha yahoo.com> writes:
== Quote from Pillsy (pillsbury gmail.com)'s article
 Andrei Alexandrescu Wrote:
 On 08/28/2010 08:29 PM, dsimcha wrote:
 An issue that comes up very frequently when trying to use
 const, pure or nothrow in generic code is lack of knowledge
 of whether the functions you're calling are
 const/pure/nothrow.  For example:
 T abs(T num) pure nothrow {
      return (num<  0) ? -1 * num : num;
 }
[...]
 A solution I propose is to allow the annotations  autoconst,
  autopure and  autonothrow for template functions.
[...]
 Does this sound like it could feasibly be implemented and
 would work well?
Yah, with the growing interest in applying qualifiers at a larger scale (and making Phobos a good example of such) this is quite timely. I've been mulling myself over a similar proposal. What I had in mind is a bit more structured - it would allow selecting a type or an expression and assessing its constness/purity. That would be done just like .sizeof and .stringof work - by defining two more special members .constof, .pureof, .nothrowof. For example: T abs(T num) (-1 * num).pureof (-1 * num).nothrowof { return (num < 0) ? -1 * num : num; }
Could something along the lines of a .qualof member work, for those times where you want to duplicate all relevant qualifiers? That seems likely to be the general case, and could considerably reduce the verbosity involved. T abs(T num) (-1 * num).qualof { return (num < 0) ? -1 * num : num; } is even shorter, and would have the possibility of also handling concerns of
constness and sharedness and the like properly.
 Cheers,
 Pillsy
My concern with proposals that require you to explicitly check specific statements is that they won't scale to larger functions where there are a lot of things that need to be checked. I'm worried some signatures might become so complicated with qualification checking as to make protected virtual abstract base pure virtual private destructors look tame. Until someone can explain how this proposal would scale at all, let alone gracefully, to needing to check the purity/constness/nothrow-ness/safety of a lot of things, I don't think we can seriously consider it.
Aug 30 2010