Re: Should the "front" range primitive be "const" ?

2018-04-09 Thread Jonathan M Davis via Digitalmars-d-learn
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:
> > [...]
> > http://jmdavisprog.com/articles/why-const-sucks.html
>
> I 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 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



Re: Should the "front" range primitive be "const" ?

2018-04-09 Thread Drone1h via Digitalmars-d-learn

On Monday, 19 March 2018 at 00:50:06 UTC, Jonathan M Davis wrote:


[...]
http://jmdavisprog.com/articles/why-const-sucks.html


I 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.


Re: Should the "front" range primitive be "const" ?

2018-03-23 Thread Drone1h via Digitalmars-d-learn

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:
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 [...]


Thank you again for your kind replies. I will carefully read 
during the following days.


Re: Should the "front" range primitive be "const" ?

2018-03-19 Thread Seb via Digitalmars-d-learn

On Friday, 2 February 2018 at 14:29:34 UTC, H. S. Teoh wrote:
On Fri, Feb 02, 2018 at 07:06:56AM +, Simen Kjærås via 
Digitalmars-d-learn 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.


[...]


FWIW there's also head const in phobos since more than one year - 
known as Final:


https://dlang.org/phobos/std_experimental_typecons.html


Re: Should the "front" range primitive be "const" ?

2018-03-18 Thread Jonathan M Davis via Digitalmars-d-learn
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.

> > [...] 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 ?

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



Re: Should the "front" range primitive be "const" ?

2018-03-18 Thread Drone1h via Digitalmars-d-learn
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 constness


I 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.



Re: Should the "front" range primitive be "const" ?

2018-02-04 Thread Simen Kjærås via Digitalmars-d-learn

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.



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


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.



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.


Yeah, I found the same bug when playing with .headMutable:
https://issues.dlang.org/show_bug.cgi?id=18260

--
  Simen


Re: Should the "front" range primitive be "const" ?

2018-02-02 Thread H. S. Teoh via Digitalmars-d-learn
On Fri, Feb 02, 2018 at 07:06:56AM +, 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.


Re: Should the "front" range primitive be "const" ?

2018-02-02 Thread H. S. Teoh via Digitalmars-d-learn
On Fri, Feb 02, 2018 at 07:06:56AM +, Simen Kjærås via Digitalmars-d-learn 
wrote:
> 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.

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.


> 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 


Re: Should the "front" range primitive be "const" ?

2018-02-01 Thread Simen Kjærås via Digitalmars-d-learn

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


Re: Should the "front" range primitive be "const" ?

2018-02-01 Thread H. S. Teoh via Digitalmars-d-learn
On Thu, Feb 01, 2018 at 07:52:32AM +, Simen Kjærås via Digitalmars-d-learn 
wrote:
> 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.
[...]

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. Constantine


Re: Should the "front" range primitive be "const" ?

2018-01-31 Thread Simen Kjærås via Digitalmars-d-learn

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


Re: Should the "front" range primitive be "const" ?

2018-01-31 Thread Steven Schveighoffer via Digitalmars-d-learn

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:

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.


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.



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


Re: Should the "front" range primitive be "const" ?

2018-01-31 Thread Jonathan M Davis via Digitalmars-d-learn
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



Re: Should the "front" range primitive be "const" ?

2018-01-31 Thread Steven Schveighoffer via Digitalmars-d-learn

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


Re: Should the "front" range primitive be "const" ?

2018-01-31 Thread H. S. Teoh via Digitalmars-d-learn
On Wed, Jan 31, 2018 at 07:08:58AM +, 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


Re: Should the "front" range primitive be "const" ?

2018-01-30 Thread Simen Kjærås via Digitalmars-d-learn

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


Re: Should the "front" range primitive be "const" ?

2018-01-30 Thread H. S. Teoh via Digitalmars-d-learn
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/cpxfgdmklgusodqou...@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


Re: Should the "front" range primitive be "const" ?

2018-01-30 Thread Jonathan M Davis via Digitalmars-d-learn
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/cpxfgdmklgusodqou...@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



Re: Should the "front" range primitive be "const" ?

2018-01-30 Thread H. S. Teoh via Digitalmars-d-learn
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/cpxfgdmklgusodqou...@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


Re: Should the "front" range primitive be "const" ?

2018-01-30 Thread Steven Schveighoffer via Digitalmars-d-learn

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


Re: Should the "front" range primitive be "const" ?

2018-01-29 Thread Jonathan M Davis via Digitalmars-d-learn
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



Should the "front" range primitive be "const" ?

2018-01-29 Thread Drone1h via Digitalmars-d-learn

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