Skip to content

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 /sbin/runscript, but I can control how the script is loaded to avoid using that default context.

Here's a program called services.sh, written in bash, that controls the context of an init script. The name of the init script is passed as the first argument.

#!/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

PDF of full graph

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

No Trackbacks

Comments

Display comments as Linear | Threaded

No comments

Add Comment

E-Mail addresses will not be displayed and will only be used for E-Mail notifications.
To leave a comment you must approve it via e-mail, which will be sent to your address after submission.
Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Form options