digitalmars.D.learn - Should the "front" range primitive be "const" ?
- Drone1h (29/29) Jan 29 2018 Hello all,
- Jonathan M Davis (23/52) Jan 29 2018 If you want to put an attribute on it, inout is better, because then it ...
- Steven Schveighoffer (13/65) Jan 30 2018 Not necessarily. A main reason for const is to advertise "I'm not going
- H. S. Teoh (24/48) Jan 30 2018 I think you're conflating a const range, which *is* pretty useless since
- Jonathan M Davis (25/55) Jan 30 2018 Digitalmars-d-learn wrote:
- Steven Schveighoffer (6/13) Jan 31 2018 Right, but that is the difference between a convention ("front is
- Jonathan M Davis (21/33) Jan 31 2018 Except that if you're the one writing the function and decided whether i...
- Steven Schveighoffer (18/53) Jan 31 2018 You are misunderstanding here. You don't put const on front for the
- H. S. Teoh (55/91) Jan 30 2018 Well, that's essentially what Simen has done, and he has code to show
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (5/6) Jan 30 2018 .headMutable. :p Head-const is something we generally want to
- H. S. Teoh (6/10) Jan 31 2018 [...]
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (9/17) Jan 31 2018 Going from RefCounted!(immutable(T)) to RefCounted!(const(T)) is
- H. S. Teoh (61/75) Feb 01 2018 [...]
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (22/32) Feb 01 2018 I'm not really saying I disagree with that, but it's just not
- H. S. Teoh (23/59) Feb 02 2018 Its semantics are not broken; it's just harder to use. Due to const
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (24/42) Feb 04 2018 If the semantics of const means that users will have to write
- Seb (4/11) Mar 19 2018 FWIW there's also head const in phobos since more than one year -
- H. S. Teoh (37/46) Feb 02 2018 [...]
- Drone1h (73/94) Mar 18 2018 First of all, thank you all for the replies. It has taken me some
- Jonathan M Davis (42/62) Mar 18 2018 Marking a empty or front with inout has most of the problems that const ...
- Drone1h (3/12) Mar 23 2018 Thank you again for your kind replies. I will carefully read
- Drone1h (14/16) Apr 09 2018 I have just read the reply and the article.
- Jonathan M Davis (6/14) Apr 09 2018 I did not write the article in response to any thread. I'd been meaning ...
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
Jan 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 ! Drone1hIf 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
Jan 29 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: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. -SteveHello 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 ! Drone1hIf 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.
Jan 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:[...]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.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.[...] 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
Jan 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 viaDigitalmars-d-learn 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.On 1/29/18 8:20 PM, Jonathan M Davis wrote:[...]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.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.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. :-PI 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
Jan 30 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
Jan 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 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 DavisExcept 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.
Jan 31 2018
On 1/31/18 7:49 PM, Jonathan M Davis wrote:On Wednesday, January 31, 2018 11:58:38 Steven Schveighoffer via Digitalmars-d-learn wrote:You are misunderstanding here. You don't put const on front for the purpose of allowing const ranges (which are useless), what it does is say that the compiler guarantees, *even if the range is mutable* that front won't modify it. That is, code like the following is rejected by the compiler: int front() const { return ++val; } In other words, it's a contract that you can read without having to examine the code saying "this won't mutate the range". Sure, you can document "front shouldn't modify the range", and use that convention, but without const, the compiler doesn't care.On 1/30/18 8:05 PM, Jonathan M Davis wrote: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.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.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.Technically, wrapping requires introspection. If you don't care about forwarding the "guarantee" of constness, then you can just tag all your functions mutable, but if you do care, then you have to introspect.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.Indeed, it's not straightforward, if you have to deal with types that aren't tagged the way they should be. In addition, const is not inferred for templates like other attributes, so you can't rely on that either. -Steve
Jan 31 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:[...]Well, that's essentially what Simen has done, and he has code to show for it.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. :-PI 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.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
Jan 30 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
Jan 30 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:[...] *facepalm* Yes, .headMutable, not .headConst. Argh... T -- VI = Visual Irritation.headConst.headMutable. :p Head-const is something we generally want to avoid.
Jan 31 2018
On Wednesday, 31 January 2018 at 01:45:57 UTC, H. S. Teoh wrote: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.Going from RefCounted!(immutable(T)) to RefCounted!(const(T)) is nice, but I'd like to see a conversion from, const(RefCounted!T) to RefCounted!(const(T)). While this cannot be done without casts, the logic can be put inside .headMutable(), and include relevant checks. This will make it much safer than having the programmer cast manually. -- Simen
Jan 31 2018
On Thu, Feb 01, 2018 at 07:52:32AM +0000, Simen Kjærås via Digitalmars-d-learn wrote:On Wednesday, 31 January 2018 at 01:45:57 UTC, H. S. Teoh wrote:[...] Hmm. I experimented with this for a bit, and found that if we limit ourselves to head-mutable, then these casts are inescapable. The problem is that if we're handed a const object like const(RefCounted!T), then there's no way we can back out to RefCounted!(const(T)) without casting, because the latter contains a mutable refcount whereas the former, due to const transitivity, must be entirely unmodifiable. If the refcount were stored in the RefCount struct itself, then we could get away with a by-value copy into RefCounted!(const(T)), but unfortunately we can't do that without breaking the refcounting semantics; the refcount must be on the payload itself, and RefCounted is basically just a smart pointer. So const(RefCounted!T), by const transitivity, can only contain a const pointer to the payload, so we're forced to use a cast to get a RefCounted!(const(T)) out of it. And on that note, this casting is NOT safe; for example, if you start with an immutable(RefCounted!T) and implicitly convert it to const(RefCounted!T), then if you cast the latter to RefCounted!(const(T)), you're now violating immutable. And there's no way you can tell from inside .headMutable whether it's safe to cast, because by the time it gets to .headMutable, the original immutable type is already "forgotten". However, if we go back to the idea of tail-const, we could potentially eliminate the need for casts and also avoid breaking immutable. Basically, the problem with writing const(RefCounted!T) is that it's a one-way street: on the scale of increasing restrictiveness, we have the series: RefCounted!T < RefCounted!(const(T)) < const(RefCounted!T) Once you've gone all the way down to const(RefCounted!T), you can no longer safely back out to RefCounted!(const(T)). So that means we want to avoid const(RefCounted!T) completely. Instead, if we standardize on a way to produce a RefCounted!(const(T)) from a RefCounted!T, then we can stop halfway down the one-way street and never need to back up. Let's say we standardize on an operation .tailConst that does these conversions: Container!T.tailConst --> Container!(const(T)) Container!(const(T)).tailConst --> Container!(const(T)) const(Container!T).tailConst --> const(Container!T) // This is allowed because it's possible to implicitly convert // immutable(T) to const(T) internally: Container!(immutable(T)).tailConst --> Container!(const(T)) immutable(Container!T).tailConst --> const(Container!T) where Container can be any template that might want to support tail-const semantics. Essentially, .tailConst becomes the mid-way stand-in for the language's built-in implicit conversions from mutable/immutable to const. So instead of passing around const(Container!T), we'd construct a Container!(const(T)) by calling .tailConst on the original container, and pass that around instead. We can also generalize this via UFCS to built-in reference types: tailConst(T*) --> const(T)* tailConst((const(T))*) --> const(T)* tailConst(const(T*)) --> const(T*) tailConst(immutable(T)*) --> const(T)* tailConst(immutable(T*)) --> const(T*) Then .tailConst becomes a standard way of constructing a tail-const type in the language. No explicit language support is needed. T -- Claiming that your operating system is the best in the world because more people use it is like saying McDonalds makes the best food in the world. -- Carl B. ConstantineI 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.Going from RefCounted!(immutable(T)) to RefCounted!(const(T)) is nice, but I'd like to see a conversion from, const(RefCounted!T) to RefCounted!(const(T)). While this cannot be done without casts, the logic can be put inside .headMutable(), and include relevant checks. This will make it much safer than having the programmer cast manually.
Feb 01 2018
On Thursday, 1 February 2018 at 18:58:15 UTC, H. S. Teoh wrote:However, if we go back to the idea of tail-const, we could potentially eliminate the need for casts and also avoid breaking immutable. Basically, the problem with writing const(RefCounted!T) is that it's a one-way street: on the scale of increasing restrictiveness, we have the series: RefCounted!T < RefCounted!(const(T)) < const(RefCounted!T) Once you've gone all the way down to const(RefCounted!T), you can no longer safely back out to RefCounted!(const(T)). So that means we want to avoid const(RefCounted!T) completely.I'm not really saying I disagree with that, but it's just not realistic. Which code would you rather write? void foo(T)(const T t) {} foo(myValue); or: void foo(T)(T t) if (isTailConst!T) {} foo(myValue.tailConst); The beauty of .headMutable is it generally doesn't affect user code. If we have to tell people not to use const(T) because its semantics are broken, we've failed. Now, if at any point in your program you have an immutable(RefCounted!T), something's gone horribly wrong - basically all of the RefCounted's semantics break down when it's immutable, and any attempt at fixing it is undefined behavior. I think we can safely disregard the problems of immutable(RefCounted!T). Once we've defined immutable(RefCounted!T) to be undefined behavior, suddenly casting from const(RefCounted!T) to RefCounted!(const(T)) is OK again. -- Simen
Feb 01 2018
On Fri, Feb 02, 2018 at 07:06:56AM +0000, Simen Kjærås via Digitalmars-d-learn wrote:On Thursday, 1 February 2018 at 18:58:15 UTC, H. S. Teoh wrote:Its semantics are not broken; it's just harder to use. Due to const transitivity, it's an all-or-nothing deal. .tailConst gives us the middle ground.However, if we go back to the idea of tail-const, we could potentially eliminate the need for casts and also avoid breaking immutable. Basically, the problem with writing const(RefCounted!T) is that it's a one-way street: on the scale of increasing restrictiveness, we have the series: RefCounted!T < RefCounted!(const(T)) < const(RefCounted!T) Once you've gone all the way down to const(RefCounted!T), you can no longer safely back out to RefCounted!(const(T)). So that means we want to avoid const(RefCounted!T) completely.I'm not really saying I disagree with that, but it's just not realistic. Which code would you rather write? void foo(T)(const T t) {} foo(myValue); or: void foo(T)(T t) if (isTailConst!T) {} foo(myValue.tailConst); The beauty of .headMutable is it generally doesn't affect user code. If we have to tell people not to use const(T) because its semantics are broken, we've failed.Now, if at any point in your program you have an immutable(RefCounted!T), something's gone horribly wrong - basically all of the RefCounted's semantics break down when it's immutable, and any attempt at fixing it is undefined behavior. I think we can safely disregard the problems of immutable(RefCounted!T).Immutable may be useless for RefCounted, but I'm thinking of containers and wrapper types in general. Today, due to ranges being basically useless when you can't mutate them, you're forced to choose between a completely mutable range, or no range at all. Having .tailConst as a standard construction lets you create a usable range that provides a compiler-verified guarantee that nobody will be able to modify range elements. Today we don't have a standard way of doing this, and so people have given up on using const with ranges. We're missing out on the guarantees that const provides. .tailConst lets us get some of those guarantees back without requiring us to tie our hands behind our backs.Once we've defined immutable(RefCounted!T) to be undefined behavior, suddenly casting from const(RefCounted!T) to RefCounted!(const(T)) is OK again.[...] The problem with this is that we're now relying on convention rather than something that can be statically verified by the compiler. Once you allow casting away const, there's no longer a guarantee that somebody didn't pass in an immutable, whether by mistake or otherwise. We know from C/C++ where programming by convention leads us. :-P T -- Having a smoking section in a restaurant is like having a peeing section in a swimming pool. -- Edward Burr
Feb 02 2018
On Friday, 2 February 2018 at 14:29:34 UTC, H. S. Teoh wrote:Its semantics are not broken; it's just harder to use. Due to const transitivity, it's an all-or-nothing deal. .tailConst gives us the middle ground.If the semantics of const means that users will have to write .tailConst all over the place, it's broken. If it means that users can't use const(T) because they actually want TailConst!T, it's broken. TailConst seems like the logical solution to the problem, but it isn't. It directly impacts user code and it leads to lots of boilerplate. In addition to .tailConst, we also need .tailImmutable. And to top it off, it just doesn't mix with const at all - if you pass it as a const parameter, it's broken. If it's part of a struct or class with const methods, it's broken. It infects every part of your codebase that touches it, it forces you to basically implement your own const system in templates, and it makes const even harder to use than it currently is. Tail-const is the more intuitive way to think of it, so if I'm wrong, please show me.True. Sadly, there's no way to tell the type system 'this type should never be immutable'. Maybe such a thing should be in the language. Meanwhile, if RefCounted!T implements .headMutable, it can check at runtime that the refcount is in writable memory.Once we've defined immutable(RefCounted!T) to be undefined behavior, suddenly casting from const(RefCounted!T) to RefCounted!(const(T)) is OK again.[...] The problem with this is that we're now relying on convention rather than something that can be statically verified by the compiler. Once you allow casting away const, there's no longer a guarantee that somebody didn't pass in an immutable, whether by mistake or otherwise. We know from C/C++ where programming by convention leads us. :-PThough the above currently doesn't compile, it seems because the compiler doesn't know how to resolve Wrapper!(const(T)) given a Wrapper!T despite the alias this.Yeah, I found the same bug when playing with .headMutable: https://issues.dlang.org/show_bug.cgi?id=18260 -- Simen
Feb 04 2018
On Friday, 2 February 2018 at 14:29:34 UTC, H. S. Teoh wrote:On Fri, Feb 02, 2018 at 07:06:56AM +0000, Simen Kjærås via Digitalmars-d-learn wrote:FWIW there's also head const in phobos since more than one year - known as Final: https://dlang.org/phobos/std_experimental_typecons.html[...]Its semantics are not broken; it's just harder to use. Due to const transitivity, it's an all-or-nothing deal. .tailConst gives us the middle ground. [...]
Mar 19 2018
On Fri, Feb 02, 2018 at 07:06:56AM +0000, Simen Kjærås via Digitalmars-d-learn wrote: [...]Which code would you rather write? void foo(T)(const T t) {} foo(myValue); or: void foo(T)(T t) if (isTailConst!T) {} foo(myValue.tailConst);[...] More thoughts on this: what if we made it so that Wrapper!T is implicitly convertible to Wrapper!(const T)? Something like this: auto foo(Wrapper,T)(Wrapper!(const(T)) t) ... struct Wrapper(T) { auto tailConst() { return Wrapper!(const(T))(...); } alias tailConst this; } void main() { Wrapper!int w; foo(w); } It will interact badly if Wrapper is already using alias this for something else, but this lets us keep the convenience of implicitly decaying to tail-const without requiring an explicit call to .tailConst. Though the above currently doesn't compile, it seems because the compiler doesn't know how to resolve Wrapper!(const(T)) given a Wrapper!T despite the alias this. However, removing `Wrapper` from the template arguments of foo() does work; and seems to have the right semantics. Seems to be just a limitation of IFTI. So calling a function that expects, say, RefCounted!(const T), with an argument of type RefCounted!T can already be made to work today, if we tie .tailConst to alias this. The fully general solution will have to wait until we can improve IFTI to support the generic case where the wrapper type is also a template parameter. Might be worth filing an enhancement against the compiler to support this? If this can be made to work, we may not even need a standard name for .tailConst, as long as the wrapper type can somehow make itself decay into its tail-const version implicitly. T -- In theory, there is no difference between theory and practice.
Feb 02 2018
First of all, thank you all for the replies. It has taken me some time to learn a bit more to be able to understand at least some parts of them. I have a further question below the quotes. On Tuesday, 30 January 2018 at 01:20:09 UTC, Jonathan M Davis wrote:On Tuesday, January 30, 2018 01:05:54 Drone1h via Digitalmars-d-learn wrote:[...] 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"). [...]If you want to put an attribute on it, inout is better, because then it will work with any constnessI am not sure whether I can make it work with "inout" instead of "const". Perhaps I am missing something. Here is my code: auto Take2 (R) (R r, size_t n) { import std.range.primitives; struct Result { private: R _r; size_t _n; size_t _i; public: property bool empty () const { return _i >= _n || _r.empty; } property auto ref front () const // Replace "const" with "inout" here ? { return _r.front; } void popFront () { ++_i; if (_i < _n) _r.popFront (); } this (R r, size_t n) { _r = r; _n = n; _i = 0; } } return Result (r, n); } unittest { import std.stdio; auto file = File ("Example", "rb"); foreach (c; file.byChunk (0x100).Take2 (5)) stdout.rawWrite (c); } I get compile error: Error: mutable method `std.stdio.File.ByChunkImpl.front` is not callable using a `const` object If I replace "const" with "inout" for the "front" function, i.e. " property auto ref front () inout", I get similar error: Error: mutable method `std.stdio.File.ByChunkImpl.front` is not callable using a `inout` object May I ask that you confirm that this is what you suggested ? Thank you.[...] const ranges are utterly useless, because they can't be mutated and thus can't be iterated. [...]I am considering a function that takes as parameter a (reference to) const range. It should be able to check whether the range is "empty" and, if not empty, it should be able to access the "front" element. Now the caller of that function can rest assured that the function is not going to modify the range by "popFront". The function might be more useful than a function that just takes as parameter a value (the result of "front") because it may be called with an empty range and it can detect that. This is similar to getting as parameter a (possibly null) pointer to a value instead of getting as parameter a value. Therefore, it seems to me that a const range might be useful. If you have already considered this and have actually seen one-step ahead of me, may I ask that you confirm, please ? Thank you respectfully.
Mar 18 2018
On Monday, March 19, 2018 00:14:11 Drone1h via Digitalmars-d-learn wrote:I am not sure whether I can make it work with "inout" instead of "const". Perhaps I am missing something....May I ask that you confirm that this is what you suggested ? Thank you.Marking a empty or front with inout has most of the problems that const has. The difference is that if the range is mutable, then the return value will be treated as mutable. The internals of the function, however, must still work with const or inout, and that's not true for most ranges. It can work just fine to mark empty or front as inout or const if you're in full control of the element types and aren't wrapping other ranges, but as soon as you start wrapping other ranges, you pretty much must give up on inout and const, because most ranges won't work with them - even to just call empty or front. And in many cases, they can't be made to work with const or inout, because that often causes serious problems with the return type. The range API simply does not include const or inout, so you can't assume that any range will compile with them, meaning that ranges that wrap other ranges can only use const or inout when they're intended to wrap a very specific set of ranges that do work with const or inout. Ranges that generically wrap other ranges cannot use const or inout, or they will not compile with many (most?) ranges.Most ranges do not work with const in any way shape or form. _Some_ will work if all you're doing is looking at is empty or front. But if all you're doing is looking at the front, in general, why pass a range? Just call front and pass it, and then the function will work with more than just ranges. Yes, if you want a function that does something like auto frontOrInit(R)(R range) { return range.empty ? ElementType!R.init : range.front; } then you can make the range const, but in general, a function is either going to be iterating over the range (so the range can't be const), or what the function is doing really has nothing to do with ranges and would be far more flexible if it just took the range's element type. Functions like frontOrInit where it would make sense to only call front or empty on a range are rare. And honestly, making a function like frontOrInit accept the range by const doesn't buy you much if a range's front and empty are const, and it makes the function useless with most ranges, because most ranges don't - and many can't - mark front or empty as const. Honestly, I think that you will be far better off if you just don't try and use const or inout with ranges. You can make it work in very restricted circumstances, but you will constantly be fighting problems where code does not compile. I'd suggest that you read this: http://jmdavisprog.com/articles/why-const-sucks.html - Jonathan M Davis[...] const ranges are utterly useless, because they can't be mutated and thus can't be iterated. [...]I am considering a function that takes as parameter a (reference to) const range. It should be able to check whether the range is "empty" and, if not empty, it should be able to access the "front" element. Now the caller of that function can rest assured that the function is not going to modify the range by "popFront". The function might be more useful than a function that just takes as parameter a value (the result of "front") because it may be called with an empty range and it can detect that. This is similar to getting as parameter a (possibly null) pointer to a value instead of getting as parameter a value. Therefore, it seems to me that a const range might be useful. If you have already considered this and have actually seen one-step ahead of me, may I ask that you confirm, please ?
Mar 18 2018
On Monday, 19 March 2018 at 00:50:06 UTC, Jonathan M Davis wrote:On Monday, March 19, 2018 00:14:11 Drone1h via Digitalmars-d-learn wrote:Thank you again for your kind replies. I will carefully read during the following days.I am not sure whether I can make it work with "inout" instead of "const". Perhaps I am missing something....May I ask that you confirm that this is what you suggested ? Thank you.Marking a empty or front with inout has most of the problems that const has. The difference is that [...]
Mar 23 2018
On Monday, 19 March 2018 at 00:50:06 UTC, Jonathan M Davis wrote:[...] http://jmdavisprog.com/articles/why-const-sucks.htmlI have just read the reply and the article. I cannot believe you have written this article in response to this thread (perhaps I am mis-interpreting the date of the article). But even if not so, thank you for clarifications and for describing root causes. I will have to read it again, and then read the other replies to my question, because I am just beginning to understand what "head-const" and "tail-const" mean and to get an idea of the problems. By this reply, I just wish to let you all know that your answers are really appreciated and that I believe they are very helpful for many programmers, even if they need time to understand them. Thank you Jonathan. Thank you all.
Apr 09 2018
On Monday, April 09, 2018 23:58:02 Drone1h via Digitalmars-d-learn wrote:On Monday, 19 March 2018 at 00:50:06 UTC, Jonathan M Davis wrote:I did not write the article in response to any thread. I'd been meaning to write it for a while. It's just that it's relevant to this thread, and pointing to the article rather than trying to explain everything that's in there again saves me time. - Jonathan M Davis[...] http://jmdavisprog.com/articles/why-const-sucks.htmlI have just read the reply and the article. I cannot believe you have written this article in response to this thread (perhaps I am mis-interpreting the date of the article). But even if not so, thank you for clarifications and for describing root causes.
Apr 09 2018