On Thu, May 08, 2014 at 02:54:32PM -0700, H. S. Teoh via Digitalmars-d wrote:
> On Thu, May 08, 2014 at 02:38:18PM -0700, Andrei Alexandrescu via 
> Digitalmars-d wrote:
> > On 5/8/14, 1:41 PM, "Luís Marques" <l...@luismarques.eu>" wrote:
[...]
> > >I agree with H. S. Teoh. Indeed, I was thinking of trying to create
> > >an alternative version of std.format which returned an InputRange,
> > >instead of taking an OutputRange. The benefit of this becomes
> > >obvious when you want to use std.format() in your own ranges, which
> > >currently impairs laziness, pipelining, avoiding memory
> > >allocations, etc.
> > 
> > Interesting. So then the range returned by format() will save
> > everything passed to it, which means...
> > 
> > int fun(int[] a)
> > {
> >    auto before = format("Before: %s", a);
> >    foreach (ref e; a) ++e;
> >    auto after = format("After: %s", a);
> >    writeln(before, "\n--->\n", after);
> > }
> > 
> > *ouch*
> [...]
> 
> Yeah, I'm not so sure it's a good idea for std.format to be lazy. But
> there are valid use cases for both, so now I'm wondering if we should
> adopt Walter's idea of using a naming convention to distinguish
> between algorithms (lazy) and functions (eager).
[...]

On second thoughts, perhaps std.format *should* be lazy, or at least,
the core implementation should be lazy, and std.format should be an
eager wrapper around it (using std.algorithm.copy to copy the result of
the underlying formatting engine to the output range).

The problem is, the current design of std.format is heavily dependent on
output ranges (e.g., user-defined types that define a toString that
takes a delegate as an output range), and changing this now is going to
cause large-scale breakage of existing code. I don't see any clean way
of continuing to support toString(delegate) without introducing yet more
hidden allocations (to buffer the output of toString, for example), if
we switch std.format to a lazy implementation.

The only way I can think of to do this is to have language-level support
for yield(), then you can hook things up this way:

        module std.format;
        auto format(A...)(string fmt, A args) {
                struct Result {
                        ...
                        @property auto front() {
                                // assume typeof(args[i]) = user-defined type
                                args[i].toString((const(char)[] data) {
                                        // This switches to a different
                                        // fiber than the one running
                                        // toString.
                                        yield(data);
                                });
                                ...
                        }
                        ...
                }
                return Result(...);
        }

        module usercode;
        struct T {
                void toString(scope void delegate(const(char)[]) sink)
                {
                        ...
                        // Each of these calls will be yielded by the
                        // sink, so they will "block" until the outer
                        // caller has processes the new data.
                        sink("abc");
                        sink("def");
                        ...
                }
        }

This requires heavy-duty yield() support in the language, though. I
don't know how likely such a thing will make it into the language...


T

-- 
Winners never quit, quitters never win. But those who never quit AND never win 
are idiots.

Reply via email to