Wednesday, June 22, 2011

Items

Let's define a new pattern!

I do a lot of programming for research, and part of what this involves is turning calculations, usually expressed as verbs, into nouns. That is, I need to keep the calculation and all it's intermediate steps around so that I can inspect them.

Alas, I am programming in Python most of the time and end up writing a lot of code like this:

class Foo:
    
    def __init__(self, tol, angle):
        self.tol   = tol
        self.angle = angle
        
        ...
        
        self.a    = a
        self.b    = b
        self.step = step

ie manually saving all the steps. But in R we can do better. Imagine we could write code like:

> Center.Find = item(

> # These define the parameters

> t =,

> x =,

>

> # These give the calculation

> theta = Arg(x),

> dtheta = diff(theta) / diff(t),

> center = median(dtheta[dethat>0])

> )

where all the variables defined in the item become named members of a list, and we can inspect all of them.

We could try something like this:

> item = function(...) {

> foo = function() {

> environment()

> }

>

> args = process.args(as.list(substitute(list(...)))[-1L])

> formals(foo) = args

>

> foo

> }

That is, the names defined in the item become function parameters, which get included in the environment. process.args is an unfortunate necessity that flips the argument list.

> process.args = function(args) {

> arg.names = names(args)

> if (is.null(arg.names)) {

> arg.names = replicate(length(args), NULL)

> }

>

> names(args) = arg.names

>

> args

> }

> A = item(

> x =,

> y =,

>

> sum = x + y

> )

No error yet...

> a = A(2, 3)

> as.list(a)

$x
[1] 2

$y
[1] 3

$sum
[1] 5

Excellent.

Notice that we get a few other features for free:

> A = item(

> sum = x + y,

>

> x =,

> y =

> )

> a = A(x=2, y=3)

> as.list(a)

$sum
[1] 5

$x
[1] 2

$y
[1] 3

Lazy evaluation means we can define variables in any order; also ones we don't use will never be evaluated. This saves me from a common dilemma: I want to include lots of diagnostic information just in case I need it, but I don't want to wait for it to be calculated. Now I can add in any extra info I can imagine using, and it won't be calculated unless I need it.

Another nice feature is you can "reach in" to the item and change its behavior -- not terribly good style, but this is research code.

> a = A(x=2, y=3, sum=8)

> as.list(a)

$sum
[1] 8

$x
[1] 2

$y
[1] 3

Notice, however, that this (which would really be the only useful purpose of "reaching in"), fails:

> a = A(x=2, y=3, sum = x - 1)

> try(as.list(a), silent=T)

> geterrmessage()

[1] "Error in as.list.environment(a) : object 'x' not found\n"

So, what if we wanted to defy common sense and make it possible to modify the behiavor of an item at instantiation time? Well remember how the result of calling an item is an environmenth? Uh oh...

> a = A(

> x = 2,

> y = 3,

> sum = with(a, x - 1)

> )

> as.list(a)

$sum
[1] 1

$x
[1] 2

$y
[1] 3

Of course this means we cannot modify the behavior of an anonymous item. I couldn't think of a way to do that...

No comments:

Post a Comment