Tuesday, 20 March 2012

local variables are great

In the last few months I have really come to appreciate what perl and bash local variables really are. And it took a better understanding of list/scheme/tex/texmacs, and a couple of years to think about it.

And my bash scripts are more and more lisp-like using extreme parallelisations and local variables.

This bash function takes the name of a variable as a first argument and returns in this argument a free file descriptor number, rather like C's fopen(). The second argument can be the lowest file descriptor to choose from, but it defaults to the current value of the variable whose name is given in the first argument. The third argument is the highest value to work to, and defaults to 254.

The shell function closes stderr as it uses stderr for fdup to test if a destructor is in use. Ironically to close stderr, bash has to save it in another file descriptor - but internally bash can find one that is free, something that this function is written to do! But this means that this function will never allocate the descriptor in temporary use to save stderr even though that descriptor will be free when this function exits. Thus while this function can allocate a free descriptor it may miss some that were free. C'est la vie.

fd_allocate() {
  set "$1" "${2:-${!1:-3}}" "${3:-254}"

  printf -v "$1" ""
  while test "$2" -le "$3"
    if ! : 2>&"$2"
    then printf -v "$1" "%s" "$2"
    set "$1" "$(( $2 + 1 ))" "$3"
  return 1
} 2>&-

This function is used for high parallelisation like this:

do_something() {
  local fd=4 things
  for thing in "$@"
    if fd_allocate fd && exec 3< <( something "$thing" )
    then eval "exec $fd<&3 3<&-"
    else # spawn failed, do foreground instead
         echo "Output for $thing:"
         something "$thing"

The results (stdout) can then be reaped like this ($? cannot be obtained, if you want it, emit it to stdout):

  local output
  for fd in ${!things[@]}
     thing = ${!things[$fd]}
     read -d "" -u $fd output
     echo "Output for $thing: $output"

Of course cat </dev/fd/$fd might do instead of read

However, all these uses of local are abusive and ideally I do want scoped variables in bash too. In fact fd_allocate was specially written to not use any local variables but to re-write it's parameters instead.

But the point is I can reap the parallised results in the right local environment without having to pass parameters. This becomes more useful as I factor my code which is written using literate programming techniques; and so I now longer write linear functions but instead compose fragments of code, and of course in this context, the environment of local or scoped variables becomes an implicit API, and so this is the situation in which I find myself understanding the power of local variables or the environment.

Local variables are visible to all subsequently called functions and so reduce the burden to pass a lot of parameters (something which C++ tries to ameliorate with default parameters) or objects of parameter sets.

Local variables are part of the environment exposed to called functions.

C could never really have local variables.

1 comment:

  1. fd_allocate can be replaced with:

    { : ; } {fd}>&1

    where fd can be replaced with the name of any variable. Don't forget to close fd with:

    eval "exec $fd>&-"