injectoR

Dependency Injection Framework for R

Injector is meant to ease development making it clear what parts of your script depend on what other functionality without cluttering your interface

define (three = function () 3,
        power = function (power)
          function (x, n)
            if (n < 1) 1 else x * power (x, n - 1));
define (cube = function (power, three)
          function (x) power (x, three));

inject (function (cube) cube (4));

Define collections to accumulate bindings and have the collection injected as a (optionally named) list. Multibindings are useful as a plugin system

add.food <- multibind ('food');

add.food (function () 'pizza');
multibind ('food') (function () 'ice cream');
add.food (pretzel = function () 'pretzel');

inject (function (food) food);

Shimming a library will define each of its globally exported variables. Shimming does not call library() so it will not export variables in the global namespace. Shimming and injecting is better than calling library() because it defines clear boundaries of dependency, and while an original result may depend on a library a derived will not have this explicit dependency allowing you to switch the original implementations at will

shim ('agrmt');

inject (function (modes) {
  # do stuff with modes()
});

shim (s4 = 'stats4', callback = function (s4.AIC) {
  # do stuff with stats4's AIC()
});

# Useful idiom for shimming libraries in 
# an anonymous binder without polluting
# the root binder (or whatever binder you're
# using)
shim (b = 'base', s = 'stats',
      callback = function (b.loadNamespace,
                           b.getNamespaceExports,
                           s.setNames) {
  # Define something useful into your root binder
  define (exports = function () function (...)
    lapply (s.setNames (nm = c (...)), function (package)
      b.getNamespaceExports (b.loadNamespace (package))));
}, binder = binder ());

inject (function (exports) exports);

You may optionally inject or provide a default value

define (greeting = function (name = "stranger")
  print (paste ("Greetings,", name)));

inject (function (greeting) {});

define (name = function () 'Bob');

inject (function (greeting) {});

You may scope your bindings

define (counter = function () {
  count <- 0;
  function () count <<- count + 1;
}, singleton);

inject (function (counter) {
  print (counter ());
});

inject (function (counter) {
  print (counter ());
});

Extensible!

# Provide your own binding environment
binder <- binder ();

define (foo = function (bar = 'bar') {
  # Factory for foo
}, scope = function (provider) {
  # The scope is called at definition time and
  # is injected with the provider function;
  # provider function takes no arguments and is
  # responsible for provisioning the dependency,
  # the scope function is responsible for
  # appropriately calling it and caching result
  # when necessary. Provider is the wrapped
  # factory injection call
}, binder = binder);