Jump to page: 1 2
Thread overview
Re: std.range.cacheFront proposal&working code: wraps a range to enforce front is called only once
Oct 24, 2013
Jonathan M Davis
Oct 24, 2013
Chris
Oct 24, 2013
Dicebot
Oct 24, 2013
Chris
Oct 24, 2013
Timothee Cour
Oct 24, 2013
Jonathan M Davis
Oct 24, 2013
Chris
Oct 24, 2013
Jonathan M Davis
Oct 24, 2013
deadalnix
Oct 24, 2013
Jonathan M Davis
Oct 24, 2013
deadalnix
Oct 25, 2013
Jonathan M Davis
Oct 24, 2013
Timothee Cour
Oct 24, 2013
deadalnix
Oct 24, 2013
Jonathan M Davis
Oct 24, 2013
Jonathan M Davis
Oct 25, 2013
Timothee Cour
Oct 25, 2013
Dicebot
Oct 27, 2013
Jonathan M Davis
October 24, 2013
On Tuesday, October 22, 2013 23:40:00 Timothee Cour wrote:
> Following the recent thread "front evaluated multiple time with joiner depending on where extra arg given", I'd like to propose the following addition in std.range :
> 
> The goal is to ensure that a given range's 'front' method is called only once per element, allowing one to handle safely side effects in 'front' methods

Considering that front really isn't supposed to have side effects, I'm inclined to think that putting something like this in the standard library is a bad idea. It just encourages bad code. There's certainly no reason why you can't implement it for your own stuff if you want to, but I don't think that it's something that should be encouraged as it goes against the range paradigm. The getter property for front really should be logically const and logically pure, and a front with side effects should be something reserved for debugging.

- Jonathan M Davis
October 24, 2013
On Thursday, 24 October 2013 at 01:48:32 UTC, Jonathan M Davis wrote:
> On Tuesday, October 22, 2013 23:40:00 Timothee Cour wrote:
>> Following the recent thread "front evaluated multiple time with joiner
>> depending on where extra arg given", I'd like to propose the following
>> addition in std.range :
>> 
>> The goal is to ensure that a given range's 'front' method is called only
>> once per element, allowing one to handle safely side effects in 'front'
>> methods
>
> Considering that front really isn't supposed to have side effects, I'm inclined
> to think that putting something like this in the standard library is a bad
> idea. It just encourages bad code. There's certainly no reason why you can't
> implement it for your own stuff if you want to, but I don't think that it's
> something that should be encouraged as it goes against the range paradigm. The
> getter property for front really should be logically const and logically pure,
> and a front with side effects should be something reserved for debugging.
>
> - Jonathan M Davis

How do you define side effects? After all ranges manipulate, reformat or restructure data.
October 24, 2013
On Thursday, 24 October 2013 at 15:36:59 UTC, Chris wrote:
> How do you define side effects? After all ranges manipulate, reformat or restructure data.

They should do it in popFront. I'd consider any `front` that does anything but accessing cached value suspicious.
October 24, 2013
On Thursday, 24 October 2013 at 15:40:22 UTC, Dicebot wrote:
> On Thursday, 24 October 2013 at 15:36:59 UTC, Chris wrote:
>> How do you define side effects? After all ranges manipulate, reformat or restructure data.
>
> They should do it in popFront. I'd consider any `front` that does anything but accessing cached value suspicious.

So code like

auto front() {
  doSomething();
  return range[0];
}

should go into

void popFront() {
  doSomething();
  range = range[1..$];
}
October 24, 2013
that changes semantics: what if you want to do some actions before and
after accessing the front element. You'd have to do quite a big of
gymnastics to do that in popFront:
eg:
elements.map!( (a) {preaction(a); auto b=transform(a); postaction(a,b);
return b;}) . filter/sort/map/etc

With what I proposed, it is possible, and it guarantees the lambda will be called once at most per element.




On Thu, Oct 24, 2013 at 8:55 AM, Chris <wendlec@tcd.ie> wrote:

> On Thursday, 24 October 2013 at 15:40:22 UTC, Dicebot wrote:
>
>> On Thursday, 24 October 2013 at 15:36:59 UTC, Chris wrote:
>>
>>> How do you define side effects? After all ranges manipulate, reformat or restructure data.
>>>
>>
>> They should do it in popFront. I'd consider any `front` that does anything but accessing cached value suspicious.
>>
>
> So code like
>
> auto front() {
>   doSomething();
>   return range[0];
> }
>
> should go into
>
> void popFront() {
>   doSomething();
>   range = range[1..$];
> }
>


October 24, 2013
On Thursday, October 24, 2013 17:55:22 Chris wrote:
> On Thursday, 24 October 2013 at 15:40:22 UTC, Dicebot wrote:
> > On Thursday, 24 October 2013 at 15:36:59 UTC, Chris wrote:
> >> How do you define side effects? After all ranges manipulate, reformat or restructure data.
> > 
> > They should do it in popFront. I'd consider any `front` that does anything but accessing cached value suspicious.
> 
> So code like
> 
> auto front() {
> doSomething();
> return range[0];
> }
> 
> should go into
> 
> void popFront() {
> doSomething();
> range = range[1..$];
> }

Yes. front _will_ be called multiple times by many range-based algorithms. There are no guarantees whatsoever that front will not be called more than once and relying on it being called only once between calls to popFront is just plain wrong.

front is the logical equivalent to a public member variable and should be treated as such. In general, putting additional work inside of front is just asking for trouble. And if you do, it needs to be with the understanding that front stands a good chance of being called multiple times per call to popFront.

It might make sense to add some debug stuff in there which has side effects (like printing something out), but the code's normal semantics should not rely on any kind of side effects on front. There should be no logical difference if you call front one time or twenty times after a single call to popFront.

- Jonathan M Davis
October 24, 2013
On Thursday, October 24, 2013 09:56:24 Timothee Cour wrote:
> that changes semantics: what if you want to do some actions before and
> after accessing the front element. You'd have to do quite a big of
> gymnastics to do that in popFront:
> eg:
> elements.map!( (a) {preaction(a); auto b=transform(a); postaction(a,b);
> return b;}) . filter/sort/map/etc
> 
> With what I proposed, it is possible, and it guarantees the lambda will be called once at most per element.

And why are you doing these preactions or postactions inside of something like map? IMHO, doing anything that doesn't involve creating the new front should be done outside of map.

Now, that being said, I'd argue that map should probably be changed so that it calls its function on popFront rather than front (though that has the downside of requiring that it then hold the current value, which it doesn't currently have to do), but regardless, you're using map for something other than what it was designed to do. It makes no guarantees of how many times the function you pass it will be called, so while it should minimize the number of calls for efficiency's sake, it's just wrong to rely on it being called only once. There is _nothing_ about ranges which makes such a guarantee (and map follows the standard range semantics without providing any additional guarantees). Logically, there is no difference between front as member function or as a member variable, and it should act like a member variable - which means no side effects. At most, it should be calculating the current value of front, but ideally, that value would be stored in order to avoid hidden costs when front ends up being called multiple times between calls to popFront.

You're obviously free to create wrappers around your ranges which do things like cache the value of front and don't call front on the wrapped range more than once per call to popFront, but when you require such a wrapper, you're violating the basic concept of how front is supposed to work per the standard range semantics. front is supposed to either be a member variable or to emulate the semantics of one, and than means no side effects. IMHO, if what you're doing requires side effects in front, then you really need to rethink how you're using ranges.

- Jonathan M Davis
October 24, 2013
On Thursday, 24 October 2013 at 01:48:32 UTC, Jonathan M Davis wrote:
> On Tuesday, October 22, 2013 23:40:00 Timothee Cour wrote:
>> Following the recent thread "front evaluated multiple time with joiner
>> depending on where extra arg given", I'd like to propose the following
>> addition in std.range :
>> 
>> The goal is to ensure that a given range's 'front' method is called only
>> once per element, allowing one to handle safely side effects in 'front'
>> methods
>
> Considering that front really isn't supposed to have side effects, I'm inclined
> to think that putting something like this in the standard library is a bad
> idea. It just encourages bad code. There's certainly no reason why you can't
> implement it for your own stuff if you want to, but I don't think that it's
> something that should be encouraged as it goes against the range paradigm. The
> getter property for front really should be logically const and logically pure,
> and a front with side effects should be something reserved for debugging.
>
> - Jonathan M Davis

range.map!(a => new Aboject(a))

And here you go. The delegate passed is pure.
October 24, 2013
On Thursday, 24 October 2013 at 17:57:54 UTC, Jonathan M Davis wrote:
> Now, that being said, I'd argue that map should probably be changed so that it
> calls its function on popFront rather than front (though that has the downside
> of requiring that it then hold the current value, which it doesn't currently
> have to do), but regardless, you're using map for something other than what it
> was designed to do.

That would break random access.
October 24, 2013
so far you've emphasized on the fact popFront should take care of all side effects and front should be logically pure and fast to compute, but you haven't suggested any alternative for what I'm trying to achieve.

In what I propose it's generic (works in all cases) and simple (just add
".cacheFront" after a lambda with side effect):

auto test(T)(T elements) if(isInputRange!T){
return elements
.map!( (a) {preaction(a); auto b=transform(a); postaction(a,b); return b;})
.cacheFront
.filter!foo; //or joiner or....
}

So, how do you achieve the above without using cacheFront, without creating
a _custom_ range or writing a lot of code ?
I don't see how using foreach is a good idea (ForEachType!=ElementType, it
doesn't reuse phobos functionality and doesn't play well with phobos range
functions)








On Thu, Oct 24, 2013 at 10:57 AM, Jonathan M Davis <jmdavisProg@gmx.com>wrote:

> On Thursday, October 24, 2013 09:56:24 Timothee Cour wrote:
> > that changes semantics: what if you want to do some actions before and
> > after accessing the front element. You'd have to do quite a big of
> > gymnastics to do that in popFront:
> > eg:
> > elements.map!( (a) {preaction(a); auto b=transform(a); postaction(a,b);
> > return b;}) . filter/sort/map/etc
> >
> > With what I proposed, it is possible, and it guarantees the lambda will
> be
> > called once at most per element.
>
> And why are you doing these preactions or postactions inside of something
> like
> map? IMHO, doing anything that doesn't involve creating the new front
> should
> be done outside of map.
>
> Now, that being said, I'd argue that map should probably be changed so
> that it
> calls its function on popFront rather than front (though that has the
> downside
> of requiring that it then hold the current value, which it doesn't
> currently
> have to do), but regardless, you're using map for something other than
> what it
> was designed to do. It makes no guarantees of how many times the function
> you
> pass it will be called, so while it should minimize the number of calls for
> efficiency's sake, it's just wrong to rely on it being called only once.
> There
> is _nothing_ about ranges which makes such a guarantee (and map follows the
> standard range semantics without providing any additional guarantees).
> Logically, there is no difference between front as member function or as a
> member variable, and it should act like a member variable - which means no
> side effects. At most, it should be calculating the current value of
> front, but
> ideally, that value would be stored in order to avoid hidden costs when
> front
> ends up being called multiple times between calls to popFront.
>
> You're obviously free to create wrappers around your ranges which do things
> like cache the value of front and don't call front on the wrapped range
> more
> than once per call to popFront, but when you require such a wrapper, you're
> violating the basic concept of how front is supposed to work per the
> standard
> range semantics. front is supposed to either be a member variable or to
> emulate the semantics of one, and than means no side effects. IMHO, if what
> you're doing requires side effects in front, then you really need to
> rethink
> how you're using ranges.
>
> - Jonathan M Davis
>


« First   ‹ Prev
1 2