Jump to page: 1 2 3
Thread overview
Should the "front" range primitive be "const" ?
Jan 30, 2018
Drone1h
Jan 30, 2018
Jonathan M Davis
Jan 30, 2018
H. S. Teoh
Jan 31, 2018
Jonathan M Davis
Feb 01, 2018
Jonathan M Davis
Jan 31, 2018
H. S. Teoh
Jan 31, 2018
Simen Kjærås
Jan 31, 2018
H. S. Teoh
Feb 01, 2018
Simen Kjærås
Feb 01, 2018
H. S. Teoh
Feb 02, 2018
Simen Kjærås
Feb 02, 2018
H. S. Teoh
Feb 05, 2018
Simen Kjærås
Mar 19, 2018
Seb
Feb 03, 2018
H. S. Teoh
Mar 19, 2018
Drone1h
Mar 19, 2018
Jonathan M Davis
Mar 24, 2018
Drone1h
Apr 09, 2018
Drone1h
Apr 10, 2018
Jonathan M Davis
January 30, 2018
Hello all,



I am trying to implement a ("struct template" ? what is the correct word ?) range that just forwards its primitives ("empty", "front", "popFront") to another range, possibly with some very limited filtering/alteration, as std.range.Take (just to learn).

Initially, the "front" member function (property) used to be declared "const", but that was not accepted when the underlying range (denoted "R" in the code below) was std.stdio.File.ByChunk ("Error: mutable method std.stdio.File.ByChunk.front is not callable using a const object").

Is there any value in having the "front" range primitive declared to be a "const"  member function ?

And if so, is the following implementation okay ? Could it be further simplified ?

    struct Taker (R)
    {
        private R _r;

        ...

        static if (functionAttributes ! (R.front) & FunctionAttribute.const_)
            public @property auto front () const { return _r.front; }
        else
            public @property auto front ()       { return _r.front; }

        ...
    }



Thank you respectfully !
Drone1h

January 29, 2018
On Tuesday, January 30, 2018 01:05:54 Drone1h via Digitalmars-d-learn wrote:
> Hello all,
>
>
>
> I am trying to implement a ("struct template" ? what is the
> correct word ?) range that just forwards its primitives ("empty",
> "front", "popFront") to another range, possibly with some very
> limited filtering/alteration, as std.range.Take (just to learn).
>
> Initially, the "front" member function (property) used to be
> declared "const", but that was not accepted when the underlying
> range (denoted "R" in the code below) was std.stdio.File.ByChunk
> ("Error: mutable method std.stdio.File.ByChunk.front is not
> callable using a const object").
>
> Is there any value in having the "front" range primitive declared to be a "const"  member function ?
>
> And if so, is the following implementation okay ? Could it be further simplified ?
>
>      struct Taker (R)
>      {
>          private R _r;
>
>          ...
>
>          static if (functionAttributes ! (R.front) &
> FunctionAttribute.const_)
>              public @property auto front () const { return
> _r.front; }
>          else
>              public @property auto front ()       { return
> _r.front; }
>
>          ...
>      }
>
>
>
> Thank you respectfully !
> Drone1h

If you want to put an attribute on it, inout is better, because then it will work with any constness, but in general, I'd suggest just avoiding the use of const or immutable altogether when dealing with ranges. front can return a const element, and that will happen if you use auto and whatever you're wrapping is const, but const ranges are utterly useless, because they can't be mutated and thus can't be iterated. As such, almost no code is ever going to have a range that is anything but mutable, which means that having front be anything but mutable is generally pointless.

The design of ranges makes them fundamentally incompatible with const. We'd had to have gone with the head/tail model where you get an entirely new object every time you pop elements off if we wanted const or immutable to work. The fact that popFront pretty much outright kills const.

If tail-const slicing were a thing for ranges, then we'd get something similar to tail/cdr out of the deal, and const ranges could be made to work, but it's a royal pain to do tail-const with user-defined types (especially templated types), and slicing an entire range like that isn't actually part of the range API. hasSlicing requires slicing indices but not the entire range. Slicing without indices operation is used on containers to get ranges but isn't used for ranges themselves.

So, as it stands at least, I'd suggest that you simply not bother using const with ranges.

- Jonathan M Davis

January 30, 2018
On 1/29/18 8:20 PM, Jonathan M Davis wrote:
> On Tuesday, January 30, 2018 01:05:54 Drone1h via Digitalmars-d-learn wrote:
>> Hello all,
>>
>>
>>
>> I am trying to implement a ("struct template" ? what is the
>> correct word ?) range that just forwards its primitives ("empty",
>> "front", "popFront") to another range, possibly with some very
>> limited filtering/alteration, as std.range.Take (just to learn).
>>
>> Initially, the "front" member function (property) used to be
>> declared "const", but that was not accepted when the underlying
>> range (denoted "R" in the code below) was std.stdio.File.ByChunk
>> ("Error: mutable method std.stdio.File.ByChunk.front is not
>> callable using a const object").
>>
>> Is there any value in having the "front" range primitive declared
>> to be a "const"  member function ?
>>
>> And if so, is the following implementation okay ? Could it be
>> further simplified ?
>>
>>       struct Taker (R)
>>       {
>>           private R _r;
>>
>>           ...
>>
>>           static if (functionAttributes ! (R.front) &
>> FunctionAttribute.const_)
>>               public @property auto front () const { return
>> _r.front; }
>>           else
>>               public @property auto front ()       { return
>> _r.front; }
>>
>>           ...
>>       }
>>
>>
>>
>> Thank you respectfully !
>> Drone1h
> 
> If you want to put an attribute on it, inout is better, because then it will
> work with any constness, but in general, I'd suggest just avoiding the use
> of const or immutable altogether when dealing with ranges. front can return
> a const element, and that will happen if you use auto and whatever you're
> wrapping is const, but const ranges are utterly useless, because they can't
> be mutated and thus can't be iterated. As such, almost no code is ever going
> to have a range that is anything but mutable, which means that having front
> be anything but mutable is generally pointless.

Not necessarily. A main reason for const is to advertise "I'm not going to change your mutable data" on a function. So reading that front is const (or inout more appropriately) can assure you front makes this guarantee.

Yes, it also allows you to call on an immutable or const range, both of which are for the most part useless.

So I would say const ranges are useless, but const members of ranges provide some value.

That being said, const is viral, as is inout. So unfortunately if you *don't* mark your functions const or inout, then wrappers need to take this into account.

-Steve
January 30, 2018
On Tue, Jan 30, 2018 at 08:54:00AM -0500, Steven Schveighoffer via Digitalmars-d-learn wrote:
> On 1/29/18 8:20 PM, Jonathan M Davis wrote:
[...]
> > If you want to put an attribute on it, inout is better, because then it will work with any constness, but in general, I'd suggest just avoiding the use of const or immutable altogether when dealing with ranges. front can return a const element, and that will happen if you use auto and whatever you're wrapping is const, but const ranges are utterly useless, because they can't be mutated and thus can't be iterated. As such, almost no code is ever going to have a range that is anything but mutable, which means that having front be anything but mutable is generally pointless.

I think you're conflating a const range, which *is* pretty useless since you can't iterate it, and a const .front, which only means "calling .front will not change the state of the range".  The latter is very possible, and potentially useful.  Well, there's also a .front that returns a const element, which means "you can't change the current element of the range". That's also possible, and useful.


> Not necessarily. A main reason for const is to advertise "I'm not going to change your mutable data" on a function. So reading that front is const (or inout more appropriately) can assure you front makes this guarantee.
> 
> Yes, it also allows you to call on an immutable or const range, both of which are for the most part useless.
> 
> So I would say const ranges are useless, but const members of ranges provide some value.
> 
> That being said, const is viral, as is inout. So unfortunately if you *don't* mark your functions const or inout, then wrappers need to take this into account.
[...]

Simen has had some ideas recently about "head mutable" aka tail-const, which could be a first step towards making const ranges, or rather tail-const ranges, actually usable:

	https://forum.dlang.org/post/cpxfgdmklgusodqouqdr@forum.dlang.org

tl;dr summary:

(1) Ranges implement a standard method, tentatively called
    opHeadMutable, that returns a head-mutable version of themselves.
    For example, const(MyRange!T).opHeadMutable would return
    MyRange!(const(T)).

(2) Standard library functions would recognize opHeadMutable and use it
    where needed, e.g., when you hand them a const range.

(3) Profit. :-P


T

-- 
In a world without fences, who needs Windows and Gates? -- Christian Surchi
January 30, 2018
On Tuesday, January 30, 2018 07:49:28 H. S. Teoh via Digitalmars-d-learn wrote:
> On Tue, Jan 30, 2018 at 08:54:00AM -0500, Steven Schveighoffer via
Digitalmars-d-learn wrote:
> > On 1/29/18 8:20 PM, Jonathan M Davis wrote:
> [...]
>
> > > If you want to put an attribute on it, inout is better, because then it will work with any constness, but in general, I'd suggest just avoiding the use of const or immutable altogether when dealing with ranges. front can return a const element, and that will happen if you use auto and whatever you're wrapping is const, but const ranges are utterly useless, because they can't be mutated and thus can't be iterated. As such, almost no code is ever going to have a range that is anything but mutable, which means that having front be anything but mutable is generally pointless.
>
> I think you're conflating a const range, which *is* pretty useless since you can't iterate it, and a const .front, which only means "calling .front will not change the state of the range".  The latter is very possible, and potentially useful.  Well, there's also a .front that returns a const element, which means "you can't change the current element of the range". That's also possible, and useful.

Except that unless front returns by ref, it really doesn't matter whether front is const unless it's violating the range API, since front is supposed to return the same value until popFront is called (or if it's assigned a new value via a front that returns by ref). So, in practice, putting const on front really doesn't help you any, and it actually hurts you for range composability.

> Simen has had some ideas recently about "head mutable" aka tail-const, which could be a first step towards making const ranges, or rather tail-const ranges, actually usable:
>
>   https://forum.dlang.org/post/cpxfgdmklgusodqouqdr@forum.dlang.org
>
> tl;dr summary:
>
> (1) Ranges implement a standard method, tentatively called
>     opHeadMutable, that returns a head-mutable version of themselves.
>     For example, const(MyRange!T).opHeadMutable would return
>     MyRange!(const(T)).
>
> (2) Standard library functions would recognize opHeadMutable and use it
>     where needed, e.g., when you hand them a const range.
>
> (3) Profit. :-P

I still need to look over what he's proposing in more detail - it's been proposed before (by Andrei IIRC) that one possible solution would be to add an operator for returning a tail-const version of a type, but no one has ever taken that idea anywhere.

Personally, I'm getting to the point that I'd rather just avoid const than deal with any further complications for ranges. In principle, I like the idea of const, but in practice, it just constantly gets in the way, and I've rarely actually seen any benefit from it in either C++ or D. I can think of one time in my entire life where const has prevented a bug for me - which was when I got the arguments backwards to C++'s std::copy function.

At least with immutable, you get implicit sharing and some optimization opportunities. In principle, const can get you some of the optimization opportunities but only in really restricted circumstances or circumstances where you could have used immutable and the code would have been the same (e.g. with int).

- Jonathan M Davis

January 30, 2018
On Tue, Jan 30, 2018 at 06:05:47PM -0700, Jonathan M Davis via Digitalmars-d-learn wrote:
> On Tuesday, January 30, 2018 07:49:28 H. S. Teoh via Digitalmars-d-learn wrote:
[...]
> > Simen has had some ideas recently about "head mutable" aka tail-const, which could be a first step towards making const ranges, or rather tail-const ranges, actually usable:
> >
> >   https://forum.dlang.org/post/cpxfgdmklgusodqouqdr@forum.dlang.org
> >
> > tl;dr summary:
> >
> > (1) Ranges implement a standard method, tentatively called
> >     opHeadMutable, that returns a head-mutable version of
> >     themselves.  For example, const(MyRange!T).opHeadMutable would
> >     return MyRange!(const(T)).
> >
> > (2) Standard library functions would recognize opHeadMutable and use
> >     it where needed, e.g., when you hand them a const range.
> >
> > (3) Profit. :-P
> 
> I still need to look over what he's proposing in more detail - it's been proposed before (by Andrei IIRC) that one possible solution would be to add an operator for returning a tail-const version of a type, but no one has ever taken that idea anywhere.

Well, that's essentially what Simen has done, and he has code to show for it.


> Personally, I'm getting to the point that I'd rather just avoid const than deal with any further complications for ranges. In principle, I like the idea of const, but in practice, it just constantly gets in the way, and I've rarely actually seen any benefit from it in either C++ or D. I can think of one time in my entire life where const has prevented a bug for me - which was when I got the arguments backwards to C++'s std::copy function.

I have been saved by const (in D) a few times when I by mistake tried mutating something that shouldn't be mutated.  But yeah, more often than not it just gets in the way.  However, my hope is that if Simen's proposal gets somewhere, it will reduce the annoyance of const and (hopefully) increase its benefits.

Note that while Simen's code example uses ranges, since that's a common blocker for using const, it extends beyond that.  For example, consider a const(RefCounted!Object).  Right now, this is unusable because you cannot update the reference count of a const object without casting const away and treading into UB territory.  But if const(RefCounted!Object).opHeadConst returned a RefCounted!(const(Object)) instead, then this could be made usable: the payload can now become const while keeping the reference count mutable.

Of course, as currently designed, the API is kinda awkward. But that's just a syntactic issue.  We could call it instead .headConst, and you'd have:

	// mutable refcount, mutable payload
	RefCounted!Object

	// mutable refcount, const payload (useful)
	RefCounted!Object.headConst --> RefCounted!(const(Object))

	// const refcount, const payload (useless)
	const(RefCounted!Object)

Standardizing .headConst means that we now have a reliable way to
construct a RefCounted!(const(Object)) from a RefCounted!Object, whereas
currently we can only construct const(RefCounted!Object), which is
unusable.  In general, this lets us construct a Template!(const(T)) from
a Template!T without needing to know what Template is.

For example, Template could take multiple parameters, like
Template!(x,T), such that the correct head-const is actually
Template!(x, const(T)). Generic code can't know this, but if .headConst
is standardized, then it provides a way for generic code to create a
Template!(x, const(T)) from a Template(x,T) without needing special
knowledge of Template's implementation.

We could even put a generic .headConst in druntime that implements the conversion for built-in types like int* -> const(int)*.  Then .headConst becomes the standard idiom to go from any type T to a head-const version of T.  Generic code that relies on .headConst would work for both built-in types and custom user types without any change.

Best of all, this doesn't even require a language change, which is a big plus.


> At least with immutable, you get implicit sharing and some optimization opportunities. In principle, const can get you some of the optimization opportunities but only in really restricted circumstances or circumstances where you could have used immutable and the code would have been the same (e.g. with int).
[...]

I haven't thought through it carefully, but if .headConst is a viable solution to the head-const problem, then conceivably we could also extend it to deal with immutable payloads too.  Then we could go from, say, RefCounted!(immutable(T)) to RefCounted!(const(T)) generically, without any casting or breaking the type system.  We could potentially expand the scope of usefulness of immutable this way, if this approach turns out to be workable.


T

-- 
If Java had true garbage collection, most programs would delete themselves upon execution. -- Robert Sewell
January 31, 2018
On Wednesday, 31 January 2018 at 01:45:57 UTC, H. S. Teoh wrote:
> .headConst

.headMutable. :p Head-const is something we generally want to avoid.

--
  Simen
January 31, 2018
On Wed, Jan 31, 2018 at 07:08:58AM +0000, Simen Kjærås via Digitalmars-d-learn wrote:
> On Wednesday, 31 January 2018 at 01:45:57 UTC, H. S. Teoh wrote:
> > .headConst
> 
> .headMutable. :p Head-const is something we generally want to avoid.
[...]

*facepalm* Yes, .headMutable, not .headConst. Argh...


T

-- 
VI = Visual Irritation
January 31, 2018
On 1/30/18 8:05 PM, Jonathan M Davis wrote:
> 
> Except that unless front returns by ref, it really doesn't matter whether
> front is const unless it's violating the range API, since front is supposed
> to return the same value until popFront is called (or if it's assigned a new
> value via a front that returns by ref). So, in practice, putting const on
> front really doesn't help you any, and it actually hurts you for range
> composability.

Right, but that is the difference between a convention ("front is supposed to...") vs. a compiler-enforced guarantee (modifying data by calling a const-tagged front is a compiler error).

If you are OK with conventions, you don't need const at all.

-Steve
January 31, 2018
On Wednesday, January 31, 2018 11:58:38 Steven Schveighoffer via Digitalmars-d-learn wrote:
> On 1/30/18 8:05 PM, Jonathan M Davis wrote:
> > Except that unless front returns by ref, it really doesn't matter
> > whether
> > front is const unless it's violating the range API, since front is
> > supposed to return the same value until popFront is called (or if it's
> > assigned a new value via a front that returns by ref). So, in practice,
> > putting const on front really doesn't help you any, and it actually
> > hurts you for range composability.
>
> Right, but that is the difference between a convention ("front is
> supposed to...") vs. a compiler-enforced guarantee (modifying data by
> calling a const-tagged front is a compiler error).
>
> If you are OK with conventions, you don't need const at all.

Except that if you're the one writing the function and decided whether it's const or not, you're also the one deciding whether it returns by ref or not. Unless you're dealing with a reference type, and it doesn't return by ref, then const doesn't protect front at all. It just affects whether it can be called on a const range.

If you're dealing with generic code, then you have less control, and const starts mattering more, since you don't necessarily know what type is being returned, and if you're returning front from an underlying range, you the choice of eixther returning it by value or returning it by auto ref in case the underlying range returned by ref and passing that refness on is desirable. But const also interacts far more badly with generic code, because the odds are pretty high that it won't work in many cases. So, while in principle, using const to actually have the guarantees is valuable, in practice, it isn't very viable, because D's const is so restrictive.

Personally, I avoid const in generic code like the plague, because unless you've restricted the types enough to know what you're dealing with and know that it will work with const, the odds are quite high that you're writing code that's going to fall flat on its face with many types.

- Jonathan M Davis

« First   ‹ Prev
1 2 3