Context-based programming
There's a style of programming that I stumbled across a while ago. I don't remember hearing about it anywhere else before, so I thought I'd write about it here.
I call it "context-based programming." Code is expressed using terms that are defined by a context, and the user can control the context and how the terms are defined.
Here's a simple example:
print(data)
Here, what happens when print
is called depends on the
context that defined print
. It could mean to display
the argument on a screen. It could also mean to send it to a
printer.
If we allow a user to pick which context to use—the one that
defines print
to display on screen versus the one that
defines it to send to a printer—then, the code is
context-based.
Here's another example, this one from OpenRC, an alternative init system originating from Gentoo.
OpenRC init scripts are written in a subset of bash. Most actions are executed within functions that are pre-defined by the OpenRC system. Here's the script for the nullmailer service on my desktop system:
#!/sbin/runscript # Copyright 1999-2014 Gentoo Foundation # Distributed under the terms of the GNU General Public License, v2 or later # snip... depend() { use net logger } # snip...
The depend
function is where a script declares its
dependencies. The use
keyword is one of the
declarations that it can use. Here, it's declaring that this service
uses the network and the system logger.
Nearly all of the code in this init script is contained in functions defined by OpenRC. This is important later.
Also, although the style is mostly declarative, it can still do traditional processing like this:
use net.* # Depend on every network interface use service1 # Always depend on service1 if [ condition ] then use service2 # Conditionally depend on service 2 fi use $other # Depend on a service named in a variable
With this approach, OpenRC is able to do everything it needs for a service. It can start and stop services, and make sure to do so as required by dependencies, and in the correct order.
Changing context
Suppose I wanted to understand the dependencies of the services on my system a little better.
Simply grepping them wouldn't work in all cases—OpenRC is actually too flexible for that, allowing other shell operations in or around declarations, as we've seen.
Luckily, OpenRC scripts happen to allow me to control their
context. Their default context is defined by the hash-bang line as
coming from
Here's a program called
#!/bin/bash # Accepts a service name as the only argument. SERVICE="$1" cd /etc/init.d # This allows shell globbing in service scripts to match OpenRC's context. source "$SERVICE" # read the service's definitions into my own context.
The source
command loads the init script into the
current context, but it does so without loading the init script's
hash-bang line. Because all of the init script's code is in
functions, including the code to start the service, I can load the
script without interfering with my system's active services.
I'll add a function to provide my own definition
of use
, then execute the depend
function
in the init script:
function use { for s in "$@" do echo "$s -> $SERVICE" done } depend
I should get a listing of all of the dependencies. And, I do:
net -> nullmailer logger -> nullmailer
If I handle all of the dependency declarations that OpenRC supports, I can use Graphviz to generate a graph of the dependencies:
{ echo "digraph deps {" for s in /etc/init.d/* do ~/services.sh "$s" done echo "}" } | dot -Tpdf > deps.pdf
Here's the complete script:
#!/bin/bash # Accepts a service name as the only argument. SERVICE="$1" cd /etc/init.d # This allows shell globbing in service scripts to match OpenRC's # context. function depend { : } source "./$SERVICE" # Read the service's definitions into my own context. # $1: direction # $@: list of services function dep { local dir="$1"; shift for s in "$@" do if [ "$dir" = "->" ] then echo "\"$s\" -> \"$SERVICE\"" else echo "\"$SERVICE\" -> \"$s\"" fi done } function use { dep "->" "$@" } function need { dep "->" "$@" } function after { : } function keyword { : } function before { : } function provide { dep "<-" "$@" } function config { : } function want { dep "->" "$@" } function shell_var { : } function fstabinfo { : } depend
Trackbacks
The author does not allow comments to this entry
Comments
Display comments as Linear | Threaded