leah blogs

April 2005

24apr2005 · Dynamic Variables in Ruby

Many Lisps provide dynamically scoped or special variables additionally to lexically scoped ones. Some (for example Elisp or ye olde MacLisp) even soley provide dynamically scoped variables. In fact, Scheme was the first lisp-based language to make lexical scoping popular.

Now, what’s the difference between dynamic and lexical scoping? Lexical scoping should be known to every Rubyist, as this is the usual (and the only one provided by default) way of scoping. Lexical scoping has the major advantage of enabling closures, that is, pieces of code that save their lexical environment. For example, in Ruby you can write:

def adder(n)
  v = 0                         # v is lexically scoped here
  lambda { v += n }             # v and n are accessed from the closure

add1 = adder(10)
p add1.call  #=> 10
p add1.call  #=> 20
p add1.call  #=> 30

add2 = adder(5)
p add2.call  #=> 5
p add2.call  #=> 10
p add2.call  #=> 15

But you already knew that. Not so with dynamic scope, and that’s why dynamic scope is usually avoided. However, there is one important and useful usage for dynamic scope: Providing contexts. For example, let’s say you write a currency converter in Elisp:

(defvar +eur2usd-factor+ 1.3068)    ; 24apr2005

(defun eur2usd (euro)
  (* +eur2usd-factor+ euro))

(eur2usd 10)                ; => 13.068
(eur2usd 0.77)              ; => 1.006236

Ok, that was easy. But not, let’s say we would like to know what our Euros would have been worth last year. Of course, we could globally change the factor, but that would be icky and we would need to be careful to change it back, so rather let’s dynamically rebind the value:

(let ((+eur2usd-factor+ 0.9267))    ; last year, maybe
  (eur2usd 10)              ; => 9.267
  (eur2usd 0.77))           ; => 0.713559

This may surprise you, but eur2usd looks up the dynamic value of +eur2usd-factor+, not the lexical value (which would have been 1.3068). let redefines the value during the execution of his body, so when eur2usd is called, +eur2usd-factor+ is actually 0.9267. However, after the let, everything is as it has been before:

(eur2usd 10)                ; => 13.068

Now, how can we translate this behavior into Ruby. I decided to go the way of using a thread-local variable (a Hash, actually) and add convenience functions to define and change and rebind them. Let’s see how above code looks in Ruby:

require 'dynamic'

Dynamic.variable :eur2usd_factor => 1.3068

def eur2usd(euro)
  euro * Dynamic.eur2usd_factor

p eur2usd(10)               # => 13.068
p eur2usd(0.77)             # => 1.006236

Dynamic.let :eur2usd_factor => 0.9267 do
  p eur2usd(10)             # => 9.267
  p eur2usd(0.77)           # => 0.713559

p eur2usd(10)               # => 13.068

Now, that was pretty easy. You basically just need to prefix your variable with Dynamic. to look it up. It’s also a nice sample how to extend Ruby to add new language features, by definining some convenient methods alone.

As you should use dynamic variables as globals only, it does no harm that Dynamic is implemented using a singleton. Dynamic is thread-safe however, in that you can’t change other thread’s environment, and get a new copy of the main thread’s environment.

Now, go ahead and grok the code. Happy hacking.

NP: The Distillers—Gypsy Rose Lee

Copyright © 2004–2022