let's talk about output ranges

Adam D. Ruppe destructionator at gmail.com
Thu Feb 6 10:26:52 PST 2014


I just slapped this together in the other thread, let me 
copy/paste it here, talk about it a bit, then I'll finish reading 
what you wrote:

struct GCSink(T) {
     // so this is a reference type
     private struct Impl {
         T[] data;
         void put(T t) { data ~= t; }
         T[] finish() { return data; }
     }
     Impl* impl;
     alias impl this;
     void start() {
         impl = new Impl;
     }
}

struct StaticSink(T) {
     T[] container;
     this(T[] c) { container = c; }
     size_t size;
     void start() { size = 0; }
     void put(T t) { container[size++] = t; }
     T[] finish() { return container[0 .. size]; }
}
StaticSink!T staticSink(T)(T[] t) {
     return StaticSink!T(t);
}

T[] toUpper(T, OR = GCSink!T)(in T[] data, OR output = OR()) {
     output.start();
     foreach(d; data)
        output.put(d & ~0x20);
     return output.finish();
}

void main() {
     import std.stdio;
     writeln(toUpper("cool")); // default: GC
     char[10] buffer;
     auto received = toUpper("cool", staticSink(buffer[])); // 
custom static sink
     assert(buffer.ptr is received.ptr);
     assert(received == "COOL");
}

====

In addition to put(), I also added start() and finish(). There's 
precedent for this in Phobos already: the std.digest output 
ranges have methods like this. Like std.digest, put builds it up, 
then finish returns the final product.

These wouldn't be required functions on all ranges. Finish might 
even return null or void, if it was a sink into a write function 
or something. It could also close a file or something like that.

But, if it does build into an array, finish ought to be defined 
to return an analogous input range (or slice) into the data it 
just built for easier chaining and basic simple usage.

(Appender in phobos has a kinda similar thing called data, but I 
think this breaks things since it might be called at any time. 
Finish, on the other hand, could be defined that any puts after 
it are invalid.

start is used to restart things. Calling start at any time should 
reset the range to reuse the buffer.


I used the Impl* on the GC one so the default worked. Ref with a 
default argument would fail because it isn't an lvalue, so that 
would kill the simplicity.


I called staticSink on the buffer to build the range... which my 
first post said I wanted to avoid but I kind like it this way 
better. It isn't a hassle to call and keeps a nice separation 
between the view and the container.


More information about the Digitalmars-d mailing list