www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - pure-ifying my code

reply "Philippe Sigaud" <philippe.sigaud gmail.com> writes:
I'm trying to put a bit of `pure` and `const`/`immutable` in my 
code, and I'm getting strange error messages.

Namely, I've a pure function, says `foo` that returns 
`std.algorithm.joiner(someValue)`:

import std.algorithm: joiner;
auto foo() pure
{
     // some internal calculation
     // creating `someValue`
     return joiner(someValue);
}

DMD tells me `foo` cannot use the impure `joiner`, due to 
`joiner`'s internal struct (Result) not having pure methods 
(`empty`/`front`/`popFront`).

Now, it seems obvious why these range methods are not pure 
(`popFront`, at least). But I don't use them in my function! I 
just return a lazy range to iterate on other ranges. My own 
function do not mutate anything, it just creates an object. A 
mutable value admittedly, but not one with global state.

I know Phobos is not using `pure` as much as it could right now, 
but I really don't see why it's causing trouble right there.
Nov 17 2013
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, November 17, 2013 10:05:30 Philippe Sigaud wrote:
 I'm trying to put a bit of `pure` and `const`/`immutable` in my
 code, and I'm getting strange error messages.
 
 Namely, I've a pure function, says `foo` that returns
 `std.algorithm.joiner(someValue)`:
 
 import std.algorithm: joiner;
 auto foo() pure
 {
      // some internal calculation
      // creating `someValue`
      return joiner(someValue);
 }
 
 DMD tells me `foo` cannot use the impure `joiner`, due to
 `joiner`'s internal struct (Result) not having pure methods
 (`empty`/`front`/`popFront`).
 
 Now, it seems obvious why these range methods are not pure
 (`popFront`, at least).
All of them should be able to be pure, as none of them access global or static variables.
 But I don't use them in my function! I
 just return a lazy range to iterate on other ranges. My own
 function do not mutate anything, it just creates an object. A
 mutable value admittedly, but not one with global state.
 
 I know Phobos is not using `pure` as much as it could right now,
 but I really don't see why it's causing trouble right there.
Likely because it's calling an impure constructor. The compiler does not currently properly infer purity for Voldemort types. https://d.puremagic.com/issues/show_bug.cgi?id=10329 Really, the compiler's attribute inferrence is pretty pathetic at this point. It only manages to infer attributes in the most basic of cases, and that's the number one reason that so little of Phobos works with pure right now. - Jonathan M Davis
Nov 17 2013
prev sibling next sibling parent reply Philippe Sigaud <philippe.sigaud gmail.com> writes:
On Sun, Nov 17, 2013 at 11:17 AM, Jonathan M Davis <jmdavisProg gmx.com> wrote:

 DMD tells me `foo` cannot use the impure `joiner`, due to
 `joiner`'s internal struct (Result) not having pure methods
 (`empty`/`front`/`popFront`).

 Now, it seems obvious why these range methods are not pure
 (`popFront`, at least).
All of them should be able to be pure, as none of them access global or static variables.
But `popFront()` changes an internal state with visible, external effects: it changes `front` return value and in the end, it will affect `empty` return value. So no, I don't consider `popFront` to be pure. But my problem is: I don't use them! I just create a struct value that happens to have them as methods. But that should not affect my own `foo` purity, right?
 But I don't use them in my function! I
 just return a lazy range to iterate on other ranges. My own
 function do not mutate anything, it just creates an object. A
 mutable value admittedly, but not one with global state.

 I know Phobos is not using `pure` as much as it could right now,
 but I really don't see why it's causing trouble right there.
Likely because it's calling an impure constructor. The compiler does not currently properly infer purity for Voldemort types. https://d.puremagic.com/issues/show_bug.cgi?id=10329 Really, the compiler's attribute inferrence is pretty pathetic at this point. It only manages to infer attributes in the most basic of cases, and that's the number one reason that so little of Phobos works with pure right now.
Ah right, a Voldemort type, I did not know they could affect purity determination. I find they regularly cause problems. Whenever I create one, I then have to extract it from its enclosing father. I'll create my own simplified version of joiner for this problem. My current code use an eager computation and I don't want that.
Nov 17 2013
parent reply "David Nadlinger" <code klickverbot.at> writes:
On Sunday, 17 November 2013 at 10:33:12 UTC, Philippe Sigaud 
wrote:
 But `popFront()` changes an internal state with visible, 
 external
 effects: it changes `front` return value and in the end, it will
 affect `empty` return value.
 So no, I don't consider `popFront` to be pure.
That's actually not a reason for them to be impure (see e.g. http://klickverbot.at/blog/2012/05/purity-in-d#pure_member_functions for my attempt on explaining that). David
Nov 17 2013
next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, November 17, 2013 11:53:57 David Nadlinger wrote:
 On Sunday, 17 November 2013 at 10:33:12 UTC, Philippe Sigaud
 
 wrote:
 But `popFront()` changes an internal state with visible,
 external
 effects: it changes `front` return value and in the end, it will
 affect `empty` return value.
 So no, I don't consider `popFront` to be pure.
That's actually not a reason for them to be impure (see e.g. http://klickverbot.at/blog/2012/05/purity-in-d#pure_member_functions for my attempt on explaining that).
I should put that on my list of articles to link people to. I'd forgotten about it. For that matter, I should probably write more articles like that myself so that I can point people to them instead of explaining the same thing over and over again. - Jonathan M Davis
Nov 17 2013
prev sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
 On Sunday, November 17, 2013 11:53:57 David Nadlinger wrote:
 That's actually not a reason for them to be impure (see e.g.
 http://klickverbot.at/blog/2012/05/purity-in-d#pure_member_functions
 for my attempt on explaining that).
I'll peruse it, thanks!
Nov 17 2013
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, November 17, 2013 11:25:33 Philippe Sigaud wrote:
 On Sun, Nov 17, 2013 at 11:17 AM, Jonathan M Davis <jmdavisProg gmx.com> 
wrote:
 DMD tells me `foo` cannot use the impure `joiner`, due to
 `joiner`'s internal struct (Result) not having pure methods
 (`empty`/`front`/`popFront`).
 
 Now, it seems obvious why these range methods are not pure
 (`popFront`, at least).
All of them should be able to be pure, as none of them access global or static variables.
But `popFront()` changes an internal state with visible, external effects: it changes `front` return value and in the end, it will affect `empty` return value. So no, I don't consider `popFront` to be pure.
None of that matters for pure. _All_ that matters for pure is that the function does not access global, mutable state - so no mutable global or static variables. _Strong_ purity requires more, but you're not going to get that with a member function anyway, unless it's immutable, which is almost never the case. If you're trying to think about functional purity when dealing with pure, you're definitely going to misunderstand it, because functional purity really has very little to do with D's pure. At this point, pure is all about indicating that the function does not access anything mutable that you don't pass to it. Anything approaching functional purity and strong purity is then merely an optimization that can be done under very limited circumstances. And since additional calls to strongly pure functions are only optimized out within a single expression (or maybe statement - I'm not sure which - but certainly not across multiple statements), it's not like calls to strongly pure functions can be optimized out very often anyway. The other big gain from pure is the ability to change the mutability of the return type of a function under some circumstances, making it more flexible, but the whole idea that extra calls to it could be optimized out is more or less a fantasy outside of mathematical functions - and it definitely doesn't work with member functions, since they're almost never immutable like they'd have to be in order to be strongly pure.
 But I don't use them in my function! I
 just return a lazy range to iterate on other ranges. My own
 function do not mutate anything, it just creates an object. A
 mutable value admittedly, but not one with global state.
 
 I know Phobos is not using `pure` as much as it could right now,
 but I really don't see why it's causing trouble right there.
Likely because it's calling an impure constructor. The compiler does not currently properly infer purity for Voldemort types. https://d.puremagic.com/issues/show_bug.cgi?id=10329 Really, the compiler's attribute inferrence is pretty pathetic at this point. It only manages to infer attributes in the most basic of cases, and that's the number one reason that so little of Phobos works with pure right now.
Ah right, a Voldemort type, I did not know they could affect purity determination. I find they regularly cause problems. Whenever I create one, I then have to extract it from its enclosing father. I'll create my own simplified version of joiner for this problem. My current code use an eager computation and I don't want that.
I think that the typical approach at this point is to just drop purity for the moment, but if you want you really want it, you are indeed going to have to implement it yourself. But we'll get there with Phobos eventually. The primary holdup is some compiler improvements, and we'll get them. It's just a question of when. - Jonathan M Davis
Nov 17 2013
next sibling parent reply =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 11/17/2013 02:55 AM, Jonathan M Davis wrote:

 And since additional calls to strongly pure functions are only 
optimized out
 within a single expression (or maybe statement - I'm not sure which - but
 certainly not across multiple statements), it's not like calls to 
strongly
 pure functions can be optimized out very often anyway.
I don't see why not. if (pure_func(a)) { // ... } // assume 'a' is not mutated here if (pure_func(a)) { // ... } There is no reason why pure_func(a) cannot be executed only once, right? Ali
Nov 17 2013
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, November 17, 2013 08:46:49 Ali Çehreli wrote:
 On 11/17/2013 02:55 AM, Jonathan M Davis wrote:
  > And since additional calls to strongly pure functions are only
 
 optimized out
 
  > within a single expression (or maybe statement - I'm not sure which - but
  > certainly not across multiple statements), it's not like calls to
 
 strongly
 
  > pure functions can be optimized out very often anyway.
 
 I don't see why not.
 
      if (pure_func(a)) {
          // ...
      }
 
      // assume 'a' is not mutated here
 
      if (pure_func(a)) {
          // ...
      }
 
 There is no reason why pure_func(a) cannot be executed only once, right?
The reason would be that the compiler doesn't do flow analysis and as I understand it, it makes zero attempt to optimize calls to pure functions outside of an expression. So, auto foo = pure_func(a) * pure_func(a); should result in only one call to pure_func, but something like auto foo = pure_func(a); auto bar = pure_func(a); will definitely result in multiple calls to pure_func. It's not that it's impossible for the compiler to do it - it's perfectly possible. But doing so would require at least basic code flow analysis, and dmd almost never does any kind of code flow analysis. So, a smarter compiler could optimize calls to strongly pure functions more than dmd does, but even then, I question that you'd get much optimized outside of example code, because I don't think that it's all that common for the same function to be called multiple times with the same arguments within a function - and you certainly can't save the result for use outside of a function, because that would mean compiler-generated memoization, which would would require storing the results of all of those function calls somewhere. - Jonathan M Davis
Nov 17 2013
next sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 11/17/2013 12:59 PM, Jonathan M Davis wrote:

 On Sunday, November 17, 2013 08:46:49 Ali Çehreli wrote:
 There is no reason why pure_func(a) cannot be executed only once, right?
The reason would be that the compiler doesn't do flow analysis
Cool. I thought that you meant a technical impossibility. :) Ali
Nov 17 2013
prev sibling parent reply "qznc" <qznc web.de> writes:
On Sunday, 17 November 2013 at 21:00:16 UTC, Jonathan M Davis 
wrote:
 will definitely result in multiple calls to pure_func. It's not 
 that it's
 impossible for the compiler to do it - it's perfectly possible. 
 But doing so
 would require at least basic code flow analysis, and dmd almost 
 never does any
 kind of code flow analysis.
I remember Walter being against flow analysis in the frontend. This is middleend optimization stuff, though. Does dmd really use no data flow analysis anywhere?
Nov 17 2013
parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, November 18, 2013 08:22:37 qznc wrote:
 On Sunday, 17 November 2013 at 21:00:16 UTC, Jonathan M Davis
 
 wrote:
 will definitely result in multiple calls to pure_func. It's not
 that it's
 impossible for the compiler to do it - it's perfectly possible.
 But doing so
 would require at least basic code flow analysis, and dmd almost
 never does any
 kind of code flow analysis.
I remember Walter being against flow analysis in the frontend. This is middleend optimization stuff, though. Does dmd really use no data flow analysis anywhere?
It has very, very limited flow analysis that it uses in constructors to avoid multiple calls to super as well as guaranteeing that nothing gets initialized multiple times in a const or immutable constructor. It may also does some very minimal flow analysis when dealing with value range propagation ( http://www.drdobbs.com/tools/value-range-propagation/229300211 ). It may also do some minimal checks for missing return statements. But it general, no, it doesn't do flow analysis, and when it does, it's very minimal. Walter does not consider it to be worth the complexity that it requires. And while in this particular case, it would certainly be much nicer for repeated calls to a pure function to be optimized within an entire function instead of just within an expression, I don't think that it actually ends up mattering much in practice, simply because it's generally rare to make the exact same call multiple times within a function. And if you do, it's trivial to save the result in a variable to be reused.
 This is middleend optimization stuff, though.
I'm not quite sure what you mean by this. There is no middle-end. We have the front-end, which is shared by dmd, gdc, and ldc, and then each compiler has its own backend. Anything D-specific is done in the front-end. So, if there are any D-specific optimizations (such as optimizations related to pure), they need to be in the front-end. - Jonathan M Davis
Nov 17 2013
next sibling parent Dmitry Olshansky <dmitry.olsh gmail.com> writes:
18-Nov-2013 11:37, Jonathan M Davis пишет:
 On Monday, November 18, 2013 08:22:37 qznc wrote:
 On Sunday, 17 November 2013 at 21:00:16 UTC, Jonathan M Davis

 wrote:
 will definitely result in multiple calls to pure_func. It's not
 that it's
 impossible for the compiler to do it - it's perfectly possible.
 But doing so
 would require at least basic code flow analysis, and dmd almost
 never does any
 kind of code flow analysis.
I remember Walter being against flow analysis in the frontend. This is middleend optimization stuff, though. Does dmd really use no data flow analysis anywhere?
The backend, of course: https://github.com/D-Programming-Language/dmd/blob/master/src/backend/gflow.c
 It has very, very limited flow analysis that it uses in constructors to avoid
 multiple calls to super as well as guaranteeing that nothing gets initialized
 multiple times in a const or immutable constructor. It may also does some very
 minimal flow analysis when dealing with value range propagation (
 http://www.drdobbs.com/tools/value-range-propagation/229300211 ). It may also
 do some minimal checks for missing return statements. But it general, no, it
 doesn't do flow analysis, and when it does, it's very minimal. Walter does not
 consider it to be worth the complexity that it requires.
I think it was about doing it in the frontend.
 And while in this particular case, it would certainly be much nicer for
 repeated calls to a pure function to be optimized within an entire function
 instead of just within an expression, I don't think that it actually ends up
 mattering much in practice, simply because it's generally rare to make the
 exact same call multiple times within a function. And if you do, it's trivial
 to save the result in a variable to be reused.
-- Dmitry Olshansky
Nov 18 2013
prev sibling parent "qznc" <qznc web.de> writes:
On Monday, 18 November 2013 at 07:37:27 UTC, Jonathan M Davis 
wrote:
 This is middleend optimization stuff, though.
I'm not quite sure what you mean by this. There is no middle-end. We have the front-end, which is shared by dmd, gdc, and ldc, and then each compiler has its own backend. Anything D-specific is done in the front-end. So, if there are any D-specific optimizations (such as optimizations related to pure), they need to be in the front-end.
For me, middle-end is stuff, which is neither specific to input language nor to output OS/architecture/runtime. Effectively, all compiler optimizations which are useful everywhere. Maybe this view is a little outdated, since most people consider LLVM has simply a backend, while I would consider it middle- and backend of a compiler.
Nov 18 2013
prev sibling parent reply "Daniel Davidson" <nospam spam.com> writes:
On Sunday, 17 November 2013 at 10:56:16 UTC, Jonathan M Davis 
wrote:
 I think that the typical approach at this point is to just drop 
 purity for the
 moment, but if you want you really want it, you are indeed 
 going to have to
 implement it yourself. But we'll get there with Phobos 
 eventually. The primary
 holdup is some compiler improvements, and we'll get them. It's 
 just a question
 of when.

 - Jonathan M Davis
Why is it that compiler improvements are the holdup? Honest question. Is it more that the approach taken for the specific implementation went beyond the capabilities of the compiler at the time (like its inferencing capabilities)? Maybe purity was not a focus at the time of writing a lot of phobos and there are not loads of testing on it where purity was a factor. If so, isn't an alternative implementation now an improvement over waiting for the compiler updates. A corollary question would be are the benefits of Voldermort types in phobos worth the purity issues they cause? From this article (http://www.drdobbs.com/cpp/voldemort-types-in-d/232901591) Walter says, regarding the pre-Voldermort version of RandomNumberGenerator: "I could mark it with the private attribute and modules other than rnd won't be able to access it. But it's still there, outside the scope of where it belongs, and other module members can still access it, private or not (in D, private declarations are not hidden from other declarations within the same module). Besides, I want it to be so clean it squeaks." Noble goal - maybe. But maybe not as noble as users' efforts to realize benefits of purity now. Purity/const/immutable when broken spread virally. Thanks Dan
Nov 18 2013
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, November 18, 2013 19:16:11 Daniel Davidson wrote:
 On Sunday, 17 November 2013 at 10:56:16 UTC, Jonathan M Davis
 
 wrote:
 I think that the typical approach at this point is to just drop
 purity for the
 moment, but if you want you really want it, you are indeed
 going to have to
 implement it yourself. But we'll get there with Phobos
 eventually. The primary
 holdup is some compiler improvements, and we'll get them. It's
 just a question
 of when.
 
 - Jonathan M Davis
Why is it that compiler improvements are the holdup? Honest question. Is it more that the approach taken for the specific implementation went beyond the capabilities of the compiler at the time (like its inferencing capabilities)? Maybe purity was not a focus at the time of writing a lot of phobos and there are not loads of testing on it where purity was a factor.
Attribute inferrence was added to the language specifically in order to make it possible for pure, nothrow, and safe to work with templates and therefore make it possible to use them with Phobos. Without it, they don't, because you're stuck either requiring a specific set of attributes (limiting what the templates work with) or duplicating the templates with different combinations of attributes. We got enough attribute inferrence to reduce the problem, but the feature has yet to be implemented enough to fully fix the problem. It's compiler improvements which made using pure with std.algorithm possible at all.
 If so,
 isn't an alternative implementation now an improvement over
 waiting for the compiler updates. A corollary question would be
 are the benefits of Voldermort types in phobos worth the purity
 issues they cause?
Voldemort types are just part of the problem. Attribute inferrence is just plain too primitive right now, and really needs to be improved. The problem is reduced if you don't used Voldemort types, but it's not fixed.
 Noble goal - maybe. But maybe not as noble as users' efforts to
 realize benefits of purity now. Purity/const/immutable when
 broken spread virally.
It's also the case that none of that has ever worked with pure, so we haven't lost anything. We just haven't yet gained what we should have gained. But the push really needs to be to improve the compiler IMHO, because without that, attribute inferrence in general just isn't going to be good enough, and if Phobos' current situation highlights how poor the attribute inferrence is, all the better, because that puts more pressure on getting it fixed. Some of the newer functions have not used Voldemort types in order to fix similar problems, but I don't think that it's worth going and changing them all just to work around a problem with the compiler. The compiler needs to be fixed. At best, what you're talking about doing is contorting Phobos' implementation in order to make it work better with the half-implemented feature of attribute inferrence, but since it's that feature that makes it possible at all, and it really needs to be fully implemented regardless, it makes by far the most sense IMHO to just finish implementing it. But when that happens is up to the compiler devs, and at this point, I'm a library dev, not a compiler dev. - Jonathan M Davis
Nov 18 2013
parent reply "Kenji Hara" <k.hara.pg gmail.com> writes:
On Monday, 18 November 2013 at 19:52:42 UTC, Jonathan M Davis 
wrote:
 On Monday, November 18, 2013 19:16:11 Daniel Davidson wrote:
 On Sunday, 17 November 2013 at 10:56:16 UTC, Jonathan M Davis
 
 wrote:
 I think that the typical approach at this point is to just 
 drop
 purity for the
 moment, but if you want you really want it, you are indeed
 going to have to
 implement it yourself. But we'll get there with Phobos
 eventually. The primary
 holdup is some compiler improvements, and we'll get them. 
 It's
 just a question
 of when.
 
 - Jonathan M Davis
Why is it that compiler improvements are the holdup? Honest question. Is it more that the approach taken for the specific implementation went beyond the capabilities of the compiler at the time (like its inferencing capabilities)? Maybe purity was not a focus at the time of writing a lot of phobos and there are not loads of testing on it where purity was a factor.
Attribute inferrence was added to the language specifically in order to make it possible for pure, nothrow, and safe to work with templates and therefore make it possible to use them with Phobos. Without it, they don't, because you're stuck either requiring a specific set of attributes (limiting what the templates work with) or duplicating the templates with different combinations of attributes. We got enough attribute inferrence to reduce the problem, but the feature has yet to be implemented enough to fully fix the problem. It's compiler improvements which made using pure with std.algorithm possible at all.
 If so,
 isn't an alternative implementation now an improvement over
 waiting for the compiler updates. A corollary question would be
 are the benefits of Voldermort types in phobos worth the purity
 issues they cause?
Voldemort types are just part of the problem. Attribute inferrence is just plain too primitive right now, and really needs to be improved. The problem is reduced if you don't used Voldemort types, but it's not fixed.
 Noble goal - maybe. But maybe not as noble as users' efforts to
 realize benefits of purity now. Purity/const/immutable when
 broken spread virally.
It's also the case that none of that has ever worked with pure, so we haven't lost anything. We just haven't yet gained what we should have gained. But the push really needs to be to improve the compiler IMHO, because without that, attribute inferrence in general just isn't going to be good enough, and if Phobos' current situation highlights how poor the attribute inferrence is, all the better, because that puts more pressure on getting it fixed. Some of the newer functions have not used Voldemort types in order to fix similar problems, but I don't think that it's worth going and changing them all just to work around a problem with the compiler. The compiler needs to be fixed. At best, what you're talking about doing is contorting Phobos' implementation in order to make it work better with the half-implemented feature of attribute inferrence, but since it's that feature that makes it possible at all, and it really needs to be fully implemented regardless, it makes by far the most sense IMHO to just finish implementing it. But when that happens is up to the compiler devs, and at this point, I'm a library dev, not a compiler dev. - Jonathan M Davis
I opened a new pull request to fix the compiler issue. https://github.com/D-Programming-Language/dmd/pull/2832 Kenji Hara
Nov 20 2013
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, November 20, 2013 10:20:35 Kenji Hara wrote:
 I opened a new pull request to fix the compiler issue.
 
 https://github.com/D-Programming-Language/dmd/pull/2832
Yay! Go Kenji. I don't know what we'd do without you. - Jonathan M Davis
Nov 20 2013
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
 But `popFront()` changes an internal state with visible, external
 effects: it changes `front` return value and in the end, it will
 affect `empty` return value.
 So no, I don't consider `popFront` to be pure.
None of that matters for pure. _All_ that matters for pure is that the function does not access global, mutable state - so no mutable global or static variables. _Strong_ purity requires more, but you're not going to get that with a member function anyway, unless it's immutable, which is almost never the case. If you're trying to think about functional purity when dealing with pure, you're definitely going to misunderstand it, because functional purity really has very little to do with D's pure. At this point, pure is all about indicating that the function does not access anything mutable that you don't pass to it.
I know the difference between functional purity and D purity. It's just I consider the range internal state to *be* something mutable that I don't pass to the member. I never considered that code like this: auto a = range.front(); auto temp = a; range.popFront(); // pure popFront()? a = range.front(); assert(temp != a); // pure popFront() modified range.front() value! could be authorized. Maybe that's because, until now, I only *read* about D purity and never tried to use it in my code.
 I think that the typical approach at this point is to just drop purity for the
 moment, but if you want you really want it, you are indeed going to have to
 implement it yourself. But we'll get there with Phobos eventually. The primary
 holdup is some compiler improvements, and we'll get them. It's just a question
 of when.
I implemented it myself without problem (~ 10 LoC). The trouble really came from `joiner` using an internal struct. I wanted to keep purity, if only to get a feeling of the kind of constraints it adds to my coding style. I regularly try to 'limit' myself to CTFE-compatible code, since I often use my code at compile-time. I would like to also learn to write pure code, as fas as possible (and drop it when it's not reasonable, of course).
Nov 17 2013
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, November 17, 2013 13:53:52 Philippe Sigaud wrote:
 But `popFront()` changes an internal state with visible, external
 effects: it changes `front` return value and in the end, it will
 affect `empty` return value.
 So no, I don't consider `popFront` to be pure.
None of that matters for pure. _All_ that matters for pure is that the function does not access global, mutable state - so no mutable global or static variables. _Strong_ purity requires more, but you're not going to get that with a member function anyway, unless it's immutable, which is almost never the case. If you're trying to think about functional purity when dealing with pure, you're definitely going to misunderstand it, because functional purity really has very little to do with D's pure. At this point, pure is all about indicating that the function does not access anything mutable that you don't pass to it.
I know the difference between functional purity and D purity. It's just I consider the range internal state to *be* something mutable that I don't pass to the member. I never considered that code like this: auto a = range.front(); auto temp = a; range.popFront(); // pure popFront()? a = range.front(); assert(temp != a); // pure popFront() modified range.front() value! could be authorized. Maybe that's because, until now, I only *read* about D purity and never tried to use it in my code.
If you just think about pure as meaning that the function doesn't access any mutable global state, then you'll have a much better grasp of pure. Essentially, it restricts the function to accessing what's passed to it directly and to global constants which can't possibly have changed their value once they were set, even through other references to them. As such, pure has nothing to do with mutation. Though it is true that it can seem a bit weird with member functions if you forget the fact that they're being passed the this pointer/reference as one of their arguments, which is the case with popFront. So, that's probably what was throwing you off. - Jonathan M Davis
Nov 17 2013
prev sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
 If you just think about pure as meaning that the function doesn't access any
 mutable global state, then you'll have a much better grasp of pure.
I'll endeavour to keep this in mind :)
 Essentially, it restricts the function to accessing what's passed to it
 directly and to global constants which can't possibly have changed their value
 once they were set, even through other references to them. As such, pure has
 nothing to do with mutation. Though it is true that it can seem a bit weird
 with member functions if you forget the fact that they're being passed the
 this pointer/reference as one of their arguments, which is the case with
 popFront. So, that's probably what was throwing you off.
I considered that a hidden *this* was passed, but I forgot that a D pure function could modify its arguments (unless the parameters are specified as const or immutable in the function definition, of course). I hope I get it now. I guess this is the kind of subject (like Unicode for example) that one only understands when one uses it in his code.
Nov 17 2013