www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - inner functions instead of scope

reply cy <dlang verge.info.tm> writes:
Someone was mentioning how in RAII you can't tell if there's an 
exception condition on destruction, but to the crazy degree that 
D inlines functions, I think something similarly concise is 
possible that solves the problem. The problem I'm talking about 
is when you want to clean up a resource, you have to reserve it 
at the beginning, then free it at the end, but freeing it might 
be different depending on if you're failing or not. Classic case 
being a database transaction.

begin();
try {
   do_stuff();
} catch(e) {
   rollback();
   throw e;
}
commit();

That's the way most people would think to do it, which has the 
problem that if "do_stuff" is a really huge block of code, then 
it's hard to remember to properly rollback and commit on the 
bottom.

There is of course the "D" way to do it.
{
   begin();
   scope(failure) rollback();
   scope(success) commit();
   do_stuff();
}

That's what I usually go with, because it's not too verbose, is 
very clear on what's going on, and related code is grouped 
together.

Some people would like to do something like this:
{
   Transaction somevariable;
   do_stuff();
}

But of course, when somevariable is destroyed, you can't tell 
whether it's because of failure or success. But it's a really 
concise syntax, and isn't split up into three parts like the 
"scope" version or the try/except mess. So, what if we could do a 
transaction that did have as simple a syntax? And it hit me that 
a big block of code could be put in an inline function, and those 
are pretty much exactly what would be called for.

void transaction(Callable)(Callable do_stuff) 
if(isCallable(Callable)) {
   begin();
   scope(failure) rollback();
   scope(success) commit();
   do_stuff();
}

...

transaction({
   big_long_complicated_code_block();
});

Once you define the "transaction" function that way, it properly 
commits or rolls back no matter what is in the function passed as 
a callable. You could even have some argument only available from 
within the callable, like:

transaction((session) {
  ...
});

then you could call session.commit() if you succeeded. But that's 
getting pretty close to "Promise" syntax, and I think the 
absolute most secure way to do it is not expose that things are 
being committed or rolled back at all, to ensure that they are 
always committed or rolled back. It shouldn't be any less 
efficient than RAII would be, since the function passed as an 
argument will surely be inlined automatically.

So, here's how I would implement transaction management for a 
database.

import d2sqlite3: Database;
import std.traits: isSomeFunction;

Database db;
static this() {
   db = Database(":memory:");
   db.execute("CREATE TABLE foo (bar INTEGER UNIQUE)");
}

void transaction(Callable)(Callable c) 
if(isSomeFunction!Callable) {
   db.execute("BEGIN");
   scope(success) db.execute("COMMIT");
   scope(failure) db.execute("ROLLBACK");
   c();
  }

void main() {
   import std.stdio: writeln;
   try {
   transaction({
	  db.execute("INSERT INTO foo VALUES (2)");
	  db.execute("INSERT INTO foo VALUES (3)");
	  db.execute("INSERT INTO foo VALUES (4)");
	  db.execute("INSERT INTO foo VALUES (5)");
	  db.execute("INSERT INTO foo VALUES (6)");
	  throw new Exception("oops");
	});
   } catch(Exception e) {}

   transaction({
	  db.execute("INSERT INTO foo VALUES (2)");
	  db.execute("INSERT INTO foo VALUES (4)");
	  db.execute("INSERT INTO foo VALUES (6)");
	});

   auto results = db.execute("SELECT bar FROM foo");
   foreach(row; results) {
	writeln(row[0]);
   }
}
May 23 2016
parent Marc =?UTF-8?B?U2Now7x0eg==?= <schuetzm gmx.net> writes:
On Tuesday, 24 May 2016 at 02:01:14 UTC, cy wrote:
 void transaction(Callable)(Callable do_stuff)
^^^^^^^^ `scope` to avoid GC
 if(isCallable(Callable)) {
   begin();
   scope(failure) rollback();
   scope(success) commit();
   do_stuff();
 }

 ...

 transaction({
   big_long_complicated_code_block();
 });
This technique is very widely used in Ruby, and I agree that it's really useful. It would be even nicer if we had trailing delegates, as Jacob Carlborg suggested [1]: db.transaction { // ... } [1] http://forum.dlang.org/post/modnbn$184o$1 digitalmars.com
May 26 2016