This post originated from an RSS feed registered with Ruby Buzz
by Christian Neukirchen.
Original Post: Dynamic Variables in Ruby
Feed Title: chris blogs: Ruby stuff
Feed URL: http://chneukirchen.org/blog/category/ruby.atom
Feed Description: a weblog by christian neukirchen - Ruby stuff
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 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
end
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:
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:
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
end
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
end
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.