www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Simplifying conversion and formatting code in Phobos

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
We've learned a lot about good D idioms since std.conv was initiated. 
And of course it was always the case that writing libraries is quite 
different from writing code to be used within one sole application. 
Consider:

* to!T(x) must work for virtually all types T and typeof(x) that are 
sensible. The natural way of doing so is to have several 
semi-specialized overloads.

* Along the way it makes sense to delegate work from the user-level 
convenient syntax to a more explicit but less convenient syntax. Hence 
the necessity of toImpl.

* The need to "please format all arguments as a string" was a natural 
necessity e.g. as a second argument to assert or enforce. Hence the 
necessity of text(x, y, z) as the concatenation of to!string(x), 
to!string(y), and to!string(z).

* FormattedWrite was necessary for fwriteln and related.

* All of these have similarities and distinctions so they may use one 
another opportunistically. The alternative is to write the same code in 
different parts for the sake of artificially separating things that in 
fact are related.

The drawback of this is taking this in as a reader and maintainer. We 
have the 'text' template which calls the 'textImpl' template which calls 
the 'to' template which calls the 'toImpl' template which calls the 
'parse' template which calls the 'FormattedWrite' template which calls 
the 'to' template. Not easy to find where the work is ultimately done.

It is a challenge to find the right balance among everything. But I'm 
sure we can do better than what we have now because of the experience 
we've gained.

If anyone would like to take a fresh look at simplifying the code 
involved, it would be quite interesting. The metrics here are simpler 
code, fewer names, simpler documentation (both internal and external), 
and less code.


Andrei
Sep 06 2016
parent reply Jack Stouffer <jack jackstouffer.com> writes:
On Tuesday, 6 September 2016 at 10:04:06 UTC, Andrei Alexandrescu 
wrote:
 The drawback of this is taking this in as a reader and 
 maintainer. We have the 'text' template which calls the 
 'textImpl' template which calls the 'to' template which calls 
 the 'toImpl' template which calls the 'parse' template which 
 calls the 'FormattedWrite' template which calls the 'to' 
 template. Not easy to find where the work is ultimately done.
Yeah, this problem ultimately comes down to healthy use of DRY in Phobos in regards to string handling code. This was always the tradeoff with DRY: with small pieces of reused code being put into functions, it makes maintenance and optimization easier, but code complexity increases. While it can be a bit hard to wrap one's head around it, once I did I found that for the most part, std.conv is correct in delegating a lot of its functionality to other functions in std.conv and std.format in particular in order to stay fast.
 fewer names, simpler documentation
I've done this a little by making all of the toImpl overloads private and therefore not show in the docs anymore. That change will appear if we have another point release some time this decade :).
Sep 07 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/7/2016 8:46 AM, Jack Stouffer wrote:
 Yeah, this problem ultimately comes down to healthy use of DRY in Phobos in
 regards to string handling code. This was always the tradeoff with DRY: with
 small pieces of reused code being put into functions, it makes maintenance and
 optimization easier, but code complexity increases. While it can be a bit hard
 to wrap one's head around it, once I did I found that for the most part,
 std.conv is correct in delegating a lot of its functionality to other functions
 in std.conv and std.format in particular in order to stay fast.
Consider a couple pulls I made for dup(): https://github.com/dlang/druntime/pull/1642 https://github.com/dlang/druntime/pull/1640 https://github.com/dlang/druntime/pull/1639 Three templates were removed with no loss in functionality. I'm pretty sure the number of dup() templates can be reduced still further. Consider the pattern of overloads: template foo(T) if (condition!T) { } template foo(T) if (!condition!T) { } It makes condition!T a user-facing constraint, which it should not be. A better pattern would be: template foo(T) { static if (condition!T) { } else { } }
Sep 07 2016
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/7/16 9:49 PM, Walter Bright wrote:
 On 9/7/2016 8:46 AM, Jack Stouffer wrote:
 Yeah, this problem ultimately comes down to healthy use of DRY in
 Phobos in
 regards to string handling code. This was always the tradeoff with
 DRY: with
 small pieces of reused code being put into functions, it makes
 maintenance and
 optimization easier, but code complexity increases. While it can be a
 bit hard
 to wrap one's head around it, once I did I found that for the most part,
 std.conv is correct in delegating a lot of its functionality to other
 functions
 in std.conv and std.format in particular in order to stay fast.
Consider a couple pulls I made for dup(): https://github.com/dlang/druntime/pull/1642 https://github.com/dlang/druntime/pull/1640 https://github.com/dlang/druntime/pull/1639 Three templates were removed with no loss in functionality. I'm pretty sure the number of dup() templates can be reduced still further. Consider the pattern of overloads: template foo(T) if (condition!T) { } template foo(T) if (!condition!T) { } It makes condition!T a user-facing constraint, which it should not be.
This looks like a nice guideline. Good work. Pushing the roof now. -- Andrei
Sep 08 2016
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 9/8/2016 5:10 AM, Andrei Alexandrescu wrote:
 Consider the pattern of overloads:

    template foo(T) if (condition!T) { }
    template foo(T) if (!condition!T) { }

 It makes condition!T a user-facing constraint, which it should not be.
This looks like a nice guideline. Good work. Pushing the roof now. -- Andrei
An example of doing it better, the put() template: https://github.com/dlang/phobos/blob/master/std/range/primitives.d#L295 Something we need to move away from, the 17 overloads of formatValue() in: https://github.com/dlang/phobos/blob/master/std/format.d#L1319 all that differ only in the constraint.
Sep 08 2016
parent Dominikus Dittes Scherkl <Dominikus.Scherkl continental-corporation.com> writes:
On Thursday, 8 September 2016 at 23:34:13 UTC, Walter Bright 
wrote:
 On 9/8/2016 5:10 AM, Andrei Alexandrescu wrote:
 Consider the pattern of overloads:

    template foo(T) if (condition!T) { }
    template foo(T) if (!condition!T) { }

 It makes condition!T a user-facing constraint, which it 
 should not be.
This looks like a nice guideline. Good work. Pushing the roof now. -- Andrei
An example of doing it better, the put() template: https://github.com/dlang/phobos/blob/master/std/range/primitives.d#L295 Something we need to move away from, the 17 overloads of formatValue() in: https://github.com/dlang/phobos/blob/master/std/format.d#L1319 all that differ only in the constraint.
Yay! Finally! This is exactly what I said in another recent thread: overloads should be avoided, especially if something is meant to be for all types - do it with static if inside one function, which in turn will have MUCH MUCH easier constraints (or none at all). I think overload is a most of the time useless C++ relict.
Sep 09 2016