January 25, 2018
On Tue, Jan 23, 2018 at 09:36:03AM +0000, Simen Kjærås via Digitalmars-d wrote: [...]
> struct R(T) {
>     T[] arr;
>     auto opHeadMutable(this This)() {
>         import std.traits : CopyTypeQualifiers;
>         return R!(CopyTypeQualifiers!(This, T))(arr);
>     }
> }
> 
> This is the code you will need to write to ensure your types can be converted to head-mutable. opHeadMutable provides both a method for conversion, and a way for the HeadMutable!T template to extract the correct type.

I like this idea quite much, actually, in spite of the lack of support for implicit conversions, which is a loss (but as you said, we can't support that without breaking a lot of existing stuff or introducing massive changes that are unlikely to be accepted by Walter & Andrei). Basically, instead of a bunch of convoluted rules with poorly-understood corner cases, we delegate the responsibility of constructing a head mutable type to the type itself, so the user code decides for itself how to construct such a thing. It's a clever idea.

In fact, if the standard implementation of opHeadMutable is basically the same across all types (or most types), it could even be provided as a mixin template in the library, then all you have to do is to `mixin headMutable` or something along those lines, and off you go.


> The actual implementation of HeadMutable!T and headMutable is available here: https://gist.github.com/Biotronic/67bebfe97f17e73cc610d9bcd119adfb
> 
> 
> My current issues with this:
> 1) I don't like the names much. I called them Decay, decay and opDecay
> for a while. Name suggestions are welcome.

I'll leave the bikeshedding to others. :-P


> 2) As mentioned above, implicit conversions would be nice, but that'd require an entirely new type of implicit conversion in addition to alias this, opDispatch, opDot and interfaces/base classes. This would require some pretty darn good reasons, and I don't think a call to headMutable() is that much of a problem.

In fact, if done correctly, I think the *lack* of implicit conversion might actually be a good thing, because the code will be more self-documenting as to what its intent really is.

	const(MyRange!T) r = ...;
	...
	auto s = r.headMutable; // <-- explicit documentation of intent

as opposed to:

	const(MyRange!T) r = ...;
	...
	MyRange!(const T) s = r; // <-- intent not as clear

While one could argue the latter is more concise and therefore more readable, the problem is that it hides the fact that user-defined code is being executed to perform the implicit conversion, and since D allows you to do all sorts of stuff with compile-time arguments, there's no guarantee that the implicit call to r.headMutable actually does what you think it does.  Having to call .headMutable explicitly makes it clear that user code is being invoked, so that there is no illusion that we're just "automatically" promoting const(Templ!T) to Templ!(const T).


> Questions:
> Is a DIP required for this? Should I create a PR implementing this for
> the range types in Phobos? What other types would benefit from this?
[...]

Since this would be introducing new symbols to Phobos, as well as, arguably, a new paradigm (or a significant extension to the existing paradigms), I think it would be best to get Andrei's attention on this issue and persuade him to support this, before submitting any PRs, lest the PR gets stuck in the queue over nitpicks and rot forever.

For one thing, I'm in favor of something in this general direction (even if it doesn't end up being this specific proposal), so that we can use const more pervasively than right now, because currently, the transitivity of const severely limits how much code can actually use it. As Jonathan David has said, many of us have pretty much given up on const because it's just too difficult to work with.  Having standard library support for .headMutable is an important first step in making const more widely usable, so that more code can benefit from its guarantees.


T

-- 
Talk is cheap. Whining is actually free. -- Lars Wirzenius
January 25, 2018
On Thursday, 25 January 2018 at 19:54:55 UTC, H. S. Teoh wrote:
> I like this idea quite much, actually, in spite of the lack of support for implicit conversions, which is a loss (but as you said, we can't support that without breaking a lot of existing stuff or introducing massive changes that are unlikely to be accepted by Walter & Andrei).

Yeah. Arrays and pointers are special, and turn into their head-mutable equivalents completely unbidden, when passed to functions. No other types in the language does that, and it seems a weird semantic to specify for a given type, especially if just for making head-mutable work.


> Basically, instead of a bunch of convoluted rules with poorly-understood corner cases, we delegate the responsibility of constructing a head mutable type to the type itself, so the user code decides for itself how to construct such a thing. It's a clever idea.

Thank you. Given D's template system is very powerful, I think a solution where the type couldn't define its own conversion wouldn't be anywhere near viable.


> In fact, if the standard implementation of opHeadMutable is basically the same across all types (or most types), it could even be provided as a mixin template in the library, then all you have to do is to `mixin headMutable` or something along those lines, and off you go.

I believe this should be possible, but https://issues.dlang.org/show_bug.cgi?id=11098 causes me headaches:

mixin template headMut()
{
    auto opHeadMutable(this This)()
    {
        import std.traits : CopyTypeQualifiers, TemplateArgsOf, TemplateOf;
        import std.meta : staticMap;

        alias Tmpl = TemplateOf!This;
        alias Args = TemplateArgsOf!This;

        template Apply(T...)
        {
            static if (is(T[0]))
                alias Apply = HeadMutable!(CopyTypeQualifiers!(This, T));
            else
                alias Apply = T; // cannot use local '__lambda1' as parameter
        }

        alias ReturnType = Tmpl!(staticMap!(Apply, Args));

        return ReturnType(this);
    }
}

Another thought: T.opHeadMutable() and the free function headMutable() do basically the same thing, and could be unified through UFCS. There'd be a free function headMutable() that works for built-in types and UDTs that don't define their own T.headMutable(), and then UDTs with T.headMutable() would Just Work™. One less moving part.


>> Questions:
>> Is a DIP required for this? Should I create a PR implementing this for
>> the range types in Phobos? What other types would benefit from this?
> [...]
>
> Since this would be introducing new symbols to Phobos, as well as, arguably, a new paradigm (or a significant extension to the existing paradigms), I think it would be best to get Andrei's attention on this issue and persuade him to support this, before submitting any PRs, lest the PR gets stuck in the queue over nitpicks and rot forever.

My thoughts exactly, and the reason I haven't yet created a PR for it.


> For one thing, I'm in favor of something in this general direction (even if it doesn't end up being this specific proposal), so that we can use const more pervasively than right now, because currently, the transitivity of const severely limits how much code can actually use it. As Jonathan David has said, many of us have pretty much given up on const because it's just too difficult to work with.  Having standard library support for .headMutable is an important first step in making const more widely usable, so that more code can benefit from its guarantees.

Thanks. I hope it can at least be a stepping stone on the way.

--
  Simen
January 29, 2018
On Thursday, 25 January 2018 at 21:33:10 UTC, Simen Kjærås wrote:
> On Thursday, 25 January 2018 at 19:54:55 UTC, H. S. Teoh wrote:
>> In fact, if the standard implementation of opHeadMutable is basically the same across all types (or most types), it could even be provided as a mixin template in the library, then all you have to do is to `mixin headMutable` or something along those lines, and off you go.
>
> I believe this should be possible

I'm wrong, of course. Consider these two types:

struct S(T) {
    T payload;
}
struct U(T) {
    T[] payload;
}

Clearly, a mixin would have problems with at least one of these. In the general case, the correlation between template parameters and member types can be arbitrarily complex, so a general solution is impossible, but even with this simple example there are problems.

I wrote up a more formal description of what I'm suggesting:

https://gist.github.com/Biotronic/c6eefeb9796309360a5e8696d91d924d

--
  Simen
March 10, 2018
On Monday, 29 January 2018 at 13:07:05 UTC, Simen Kjærås wrote:
> On Thursday, 25 January 2018 at 21:33:10 UTC, Simen Kjærås wrote:
>> On Thursday, 25 January 2018 at 19:54:55 UTC, H. S. Teoh wrote:
>>> In fact, if the standard implementation of opHeadMutable is basically the same across all types (or most types), it could even be provided as a mixin template in the library, then all you have to do is to `mixin headMutable` or something along those lines, and off you go.
>>
>> I believe this should be possible
>
> I wrote up a more formal description of what I'm suggesting:
>
> https://gist.github.com/Biotronic/c6eefeb9796309360a5e8696d91d924d
>
> --
>   Simen


@headUnqualified
struct HeadUnqualifiedType1
{
	HeadUnqualifiedType2 field;
}

tailQualifierHeadUnqualified(const) HeadUnqualifiedType1 a;

//Stupid opaque data types.


1 2
Next ›   Last »