write, toString, formatValue & range interface

spir denis.spir at gmail.com
Wed Dec 15 01:48:43 PST 2010


On Tue, 14 Dec 2010 09:35:20 -0500
"Robert Jacques" <sandford at jhu.edu> wrote:

> Having recently run into this without knowing it,

Which one? (This issue causes 3 distinct bugs.)

> vote++. Also, please  
> file a bug report (or two).

Done: http://d.puremagic.com/issues/show_bug.cgi?id=5354  -- see also below


Denis


====================== text of issue report ==========================
formatValue: range templates introduce 3 bugs related to class & struct cases

This issue concerns class case, the struct case, and the 3 range cases of the set of formatValue templates in std.format. As this set is currently written and commented (1), it seems to be intended to determine the following cases (about class/struct/range only):

* An input range is formatted like an array.
* A class object is formatted using toString.
* A struct is formatted:
    ~ using an input range interface, if it implements one,
    ~ using toString, if it defines it,
    ~ in last resort, using the type's 'stringof' property.

To be short: I think the right thing to do is to remove range cases. Explanations, details, & reasoning below.

In the way the set of templates is presently implemented, and because of how template selection works (as opposed to inheritance, eg), the following 3 bugs come up:

1. When a class defines an input range, compiler-error due to the fact that both class and input range cases match:
    /usr/include/d/dmd/phobos/std/format.d(1404): Error: template std.format.formatValue(Writer,T,Char) if (is(const(T) == const(void[]))) formatValue(Writer,T,Char) if (is(const(T) == const(void[]))) matches more than one template declaration, /usr/include/d/dmd/phobos/std/format.d(1187):formatValue(Writer,T,Char) if (isInputRange!(T) && !isSomeString!(T) && isSomeChar!(ElementType!(T))) and /usr/include/d/dmd/phobos/std/format.d(1260):formatValue(Writer,T,Char) if (is(T == class))
This, due to inheritance from Object, even if no toString is _explicitely_ defined.

2. For a struct, a programmer-defined output format in toString is shortcut if ever the struct implements a range interface!

3. If a range's element type (result type of front) is identical to the range's own type, writing runs into an infinite loop... This is well possible, for instance a textual type working like strings in high-level/dynamic languages (a character is a singleton string).

To solve these bugs, I guess the following changes would have to be done:
* The 3 ranges case must have 2 additional _negative_ constraints:
    ~ no toString defined on the type
    ~ (ElementType!T != T)
* The struct case must be split in 2 sub-cases:
    ~ use toString if defined
    ~ [else use range if defined, as given above]
    ~ if neither toString nore range, use T.stringof

I have tried to implement and test this modif, but ran into build errors (seemingly unrelated, about isTuple) I could not solve.

Now, I think it is worth wondering whether all these complications, only to have _default_ formatValue's for input ranges, is worth it at all. On one hand, in view of the analogy, it looks like a nice idea to have them expressed like arrays. On the other, when can this feature be useful?
An first issue comes up because there is no way, AFAIK, to tell apart inherited and explicite toString methods of classes: is(typeof(val.toString() == string)) is always true for a class. So that the range case would never be triggered for classes -- only for structs.
So, to use this feature, (1) the type must be a struct (2) which defines no toString (3) whch implements a range interface, and (4) the range's element type must not be the range type itself. In addition, the most sensible output form for it should be precisely the one of an array.
Note that unlike for structs, programmers cannot define custom forms of array output ;-) This is the reason why a default array format is so helpful -- but this reason does not exist for structs, thank to toString (and later writeTo).
If no default form exists for ranges, then in the rare cases where a programmer would implement a range interface on a struct _and_ need to re-create an array-like format for it, this takes a few lines in toString, for instance:
    string toString () {
        string[] contents = new string[this.elements.length]; 
        foreach (i,e ; this.elements)
            contents[i] = to!string(this.elements[i]);
        return format("[%s]", join(contents, ", "));
    }

As a conclusion, I would recommend to get rid of the (3) range cases in the set of formatValue templates. (This would directly restore correctness, I guess --showing that range cases where probably added later.)

I marked the bug(s) with keyword 'spec', as it depends on: how do we want
struct/class/range formatting semantics to be?

(1) There is at least a doc/comment error, namely for the struct case (commentted as AA instead). Also, the online doc does not hold template constraints, so that it is not possible to determine which one is selected in given situations.
-- -- -- -- -- -- --
vit esse estrany ☣

spir.wikidot.com



More information about the Digitalmars-d mailing list