Generality creep

Rubn where at is.this
Tue Mar 19 20:31:11 UTC 2019


On Tuesday, 19 March 2019 at 02:52:34 UTC, Andrei Alexandrescu 
wrote:
> Walter and I were looking at indexOf in the standard library. 
> It has several overloads, of which let's look at the first two:
>
> https://dlang.org/library/std/string/index_of.html
>
> Now, why couldn't they be merged into a single function with 
> this constraint (their bodies are identical):
>
> ptrdiff_t indexOf(Range) (
>   Range s,
>   dchar c,
>   CaseSensitive cs = Yes.caseSensitive
> )
> if (isInputRange!Range && isSomeChar!(ElementType!Range));
>
> It makes sense to look for a character in any range of 
> characters.
>
> Attempting to build that fails because of this type fails to 
> work:
>
>     struct TestAliasedString
>     {
>         string get() @safe @nogc pure nothrow { return _s; }
>         alias get this;
>         @disable this(this);
>         string _s;
>     }
>
> The intuition is that the function should be general enough to 
> figure out that, hey, TestAliasedString is kinda sorta a 
> subtype of string. So the call should work.
>
> So let's see why it doesn't work - i.e. why is 
> TestAliasedString an input range? The definition of 
> isInputRange (in std.range.primitives) is:
>
> enum bool isInputRange(R) =
>     is(typeof(R.init) == R)
>     && is(ReturnType!((R r) => r.empty) == bool)
>     && is(typeof((return ref R r) => r.front))
>     && !is(ReturnType!((R r) => r.front) == void)
>     && is(typeof((R r) => r.popFront));
>
> Turns out the second clause fails. That takes us to the 
> definition of empty in the same module:
>
> @property bool empty(T)(auto ref scope const(T) a)
> if (is(typeof(a.length) : size_t))
> {
>     return !a.length;
> }
>
> The intent is fairly clear - if a range defines empty as a 
> size_t (somewhat oddly relaxed to "convertible to size_t"), 
> then empty can be nicely defined in terms of length. Cool. But 
> empty doesn't work with TestAliasedString due to an overlooked 
> matter: the "const". A mutable TestAliasedString converts to a 
> string, but a const or immutable TestAliasedString does NOT 
> convert to a const string! So this fixes that matter:
>
>     struct TestAliasedString
>     {
>         string get() @safe @nogc pure nothrow { return _s; }
>         const(string) get() @safe @nogc pure nothrow const { 
> return _s; }
>         alias get this;
>         @disable this(this);
>         string _s;
>     }
>
> That makes empty() work, but also raises a nagging question: 
> what was the relationship of TestAliasedString to string before 
> this change? Surely that wasn't subtyping. (My response would 
> be: "Odd.") And why was Phobos under the obligation to cater 
> for such a type and its tenuous relationship to a range?
>
> But wait, there's more. Things still don't work because of 
> popFront. Looking at its definition:
>
> void popFront(C)(scope ref inout(C)[] str) @trusted pure nothrow
> if (isNarrowString!(C[]))
> { ... }
>
> So, reasonably this function takes the range by reference so it 
> can modify its internals. HOWEVER! The implementation of 
> TestAliasedString.get() returns an rvalue, i.e. it's the 
> equivalent of a conversion involving a temporary. Surely that's 
> not to match, whether in the current language or the one after 
> the rvalue DIP.
>
> The change that does make the code work is:
>
>     struct TestAliasedString
>     {
>         ref string get() @safe @nogc pure nothrow { return _s; }
>         ref const(string) get() @safe @nogc pure nothrow const 
> { return _s; }
>         alias get this;
>         @disable this(this);
>         string _s;
>     }
>
> This indeed does implement a subtyping relationship, and passes 
> the isInputRange test.
>
> What's the moral of the story here? Generality is good, but it 
> seems in several places in phobos (of which this is just one 
> example), a combination of vague specification and aiming for a 
> nice ideal of "work with anything remotely reasonable" has 
> backfired into a morass of inconsistently defined and supported 
> corner cases.
>
> For this case in particular - I don't think we should support 
> all types that support some half-hearted form of subtyping, at 
> the cost of reducing generality and deprecating working code.

So basically one more reason to avoid const entirely?


More information about the Digitalmars-d mailing list