aka I finally found a use for mixin templates :p

When I can batch up operations and run them quicker all at once, I usually make a list of those operations and append to it in lieu of calling the operation. Then periodically flushing the list, and actually calling the operation. Database insertions, or disk writes, or network sends, or unit tests, or something like that. Lazily caching operations to all fire at once, basically. So, I wrote a thing to do that in D, and it actually works pretty well. Thought I'd share it.

Basically it works like this:

struct Foo {
  void direct_operation(int a, int b) { ... }
  void transaction(Callable)(Callable inside) {
    setup();
    scope(exit) takedown();
    inside();
  }
  mixin template Batch!(transaction,direct_operation) operation;
}

...

Foo foo;
foo.operation(1,2);
foo.operation(3,4);
foo.operation(5,6);
foo.operation.flush();
foo.operation(7,8);

And here's the actual code:

mixin template Batch(alias transaction, alias operation, size_t max = 0x10) {
        import std.typecons: Tuple, tuple;
        import std.traits: Parameters,ReturnType;
        import std.array: Appender;
        
        static assert(is(ReturnType!operation == void));
        alias Item = Tuple!(Parameters!operation);
        Appender!(Item[]) cache;
        void opCall(Parameters!operation args) {
                cache.put(tuple(args));
                if(cache.data.length > max)
                        flush();
        }
        void flush() {
                if(cache.data.length == 0) return;
                scope(exit) cache.shrinkTo(0);
                transaction({
                                foreach(ref args; cache.data) {
                                        operation(args.expand);
                                }
                        });
        }
        void completely(Handle)(Handle handle) {
                scope(exit) flush();
                handle();
        }
}

mixin template Batch(alias setup, alias operation, alias takedown, size_t max = 0x10) {
        void transaction(Callable)(Callable inside) {
                setup();
                scope(exit) takedown();
                inside();
        }
        mixin Batch!(transaction,operation,max);
}


unittest {
        import print: print;
        struct Foo {
                void batchable_operation(int a, int b) {
                        print("operate on",a,b);
                }
                void setup() {
                        print("setup for flushing");
                }
                void commit() {
                        print("commit");
                }
                void opCall(int a, int b) {
                        print("oops",a,b);
                }
                mixin Batch!(setup,
                                                                 
batchable_operation,
                                                                 commit,
                                                                 0x10) C;
        }

        Foo foo;

        foo.completely({
                        for(int i=0;i<20;++i) {
                                foo.C(i,i*2);
                                // .C isn't needed... except when the struct 
implements the
                                // same operation, in which case that's the 
default!
                                foo(i,i*3);
                                print("did we do it?",i);
                        }
                });
        print("done");

        struct Bar {
                void transaction(Callable)(Callable inside) {
                        print("beginning");
                        scope(exit) print("ending");
                        inside();
                }
                void batchable_operation(int a, int b) {
                        print("bar on",a,b);
                }
                mixin Batch!(transaction,
                                                                 
batchable_operation) C;
        }

        Bar bar;
        bar.completely({
                        for(int i=0;i<20;++i) {
                                bar(i,i*2);
                        }
                });
}

Reply via email to