global vs context variable

luka8088 luka8088 at owave.net
Wed Dec 11 00:21:35 PST 2013


Hi everyone!

I would like to address the issue of global variables (or states). In
general my opinion is that they are bad solely because they (in most
cases) lack the ability of alternative values (or states) or ability to
alter them in user friendly way.

For example, take write function from std.stdio. For a third party
function that uses write to output to screen user is unable to redirect
that output to, for example, file without altering third party
function's body. Or if there is a way it rarely user friendly. For
example, in php there are function for buffering output but you have to
manually start buffering and manually stop it. In python active output
is a global variable and you are able to replace it with a custom file
stream but you are responsible maintain a custom stack in case of
recursion and switching it back to default output when no longer needed.
There are also examples of loggers, database connections, etc.

I have a proposal to generalize this issue. Attached is a example
library that implements context based approach and I would like to see
this kind of library in phobos (std.context). It is very simple and yet
in my experience it has shown to be very useful.

Examples using such library:

void writeOutput () {
  writeln("example output");
}

void main () {

  writeOutput();

  standardOutputContext(file("example.txt"), {
    writeOutput();
  });

}

MVC example:

void databaseToView () {
  auto result = db.query("select;");
  view.populate(result);
}

void myAction () {

  auto customView = new View();

  viewContext(customView, {
    databaseToView();
  });

  view.regionA.append(customView);

}

void main () {

  dbContext(defaultDbConnection {
    viewContext(defaultView, {
      myAction();
    });
  });

}


I would like to add this to phobos and document it but I would like to
know if this is desirable at all and to get a community feedback.

Thoughts?
-------------- next part --------------

module context;

import std.traits;

class context (elementType) {

  private static elementType[] stack;
  private static elementType current;

  static auto opCall (callbackType) (callbackType f) if (isCallable!callbackType) {
    return opCall(elementType.init, f);
  }

  static auto opCall (callbackType) (elementType initValue, callbackType f) if (isCallable!callbackType) {
    enter(initValue);
    scope(exit) exit();
    return f();
  }

  static void enter () {
    enter(elementType.init);
  }

  static void enter (elementType initValue) {
    if (stack.length > 0)
      stack[$ - 1] = current;
    stack ~= initValue;
    current = stack[$ - 1];
  }

  static exit () {
    stack = stack[0 .. $ - 1];
    if (stack.length > 0)
      current = stack[$ - 1];
  }

  @property static bool isSet () {
    return stack.length > 0;
  }

}

unittest {

  alias context!int myCounterContext;
  alias myCounterContext.current myCounter;

  myCounterContext(1, {
    myCounterContext(2, {
      myCounterContext(3, {
        assert(myCounter == 3);
      });
      assert(myCounter == 2);
    });
    assert(myCounter == 1);
  });

}

unittest {

  alias context!int myCounterContext;
  alias myCounterContext.current myCounter;

  void f1 () {
    assert(myCounter == 1);
  }

  myCounterContext(1, {
    assert(myCounter == 1);
    f1();
  });

}

unittest {

  alias context!int myCounterContext;
  alias myCounterContext.current myCounter;

  myCounterContext({
    assert(myCounter == 0);
    myCounter = 1;

    myCounterContext({
      assert(myCounter == 0);
      myCounter = 2;

      myCounterContext({
        assert(myCounter == 0);
        myCounter = 3;
        assert(myCounter == 3);
      });

      myCounterContext({
        assert(myCounter == 0);
        myCounter = 4;
        assert(myCounter == 4);
      });

      assert(myCounter == 2);
    });

    assert(myCounter == 1);
  });

}

unittest {

  alias context!(Object) myCounterContext;
  alias myCounterContext.current myCounter;

  myCounterContext({
    assert(myCounter is null);

    myCounterContext({
      assert(myCounter is null);
      myCounter = new Object();
      assert(myCounter !is null);
    });

    assert(myCounter is null);
    myCounter = new Object();

    myCounterContext({
      assert(myCounter is null);
      myCounter = new Object();
      assert(myCounter !is null);
    });

    assert(myCounter !is null);
  });

}

unittest {

  struct counterA { int value; alias value this; }
  struct counterB { int value; alias value this; }

  alias context!(counterA) myCounterAContext;
  alias myCounterAContext.current myCounterA;

  alias context!(counterB) myCounterBContext;
  alias myCounterBContext.current myCounterB;

  myCounterAContext({
    myCounterA = 1;

    myCounterBContext({
      myCounterB = 2;
      assert(myCounterA == 1);
      myCounterA = 3;
    });

    assert(myCounterA == 3);
  });

}


More information about the Digitalmars-d mailing list