Thursday, June 12, 2008

Let There Be Local Variables

Actually, there aren't any. There are only function parameters. But there's a workaround: use functions.



Suppose you're writing a token analyser for some input. 'token-generator is a generator function. You need to determine the type of a token - return 'string if you think it's a string, 'char, 'int, etc. Naïvely, you might



(def token-type (token-generator)
(get-token-type (token-generator)))

(def get-token-type (tok)
(if (is (tok 0 #\")) 'string
(is (tok 0 #\#) 'char)
(is-digit (tok 0)) 'int))


So you get your input from 'token-generator, but you're really interested in 'tok. So the function 'get-token-type provides the local variable 'tok for you to work with. The disadvantage is that your namespace gets polluted with a zillion little functions that are only useful to their immediate neighbours. Wouldn't it be nice to, like, click Refactor > Inline Method like you can in a real language?



Well, you can.



(def token-type (token-generator)
((fn (tok)
(if (is (tok 0 #\")) 'string
(is (tok 0 #\#) 'char)
(is-digit (tok 0) 'int))) (token-generator)))


The ( (fn (tok) (if blah)) (token-generator) ) is a call to an anonymous function that declares the 'tok parameter for you to work with.



Now your namespace is no longer polluted, but you have this damned ugly thing in the middle of your function. No ordinary human is expected to read this.



Enter 'let



'let is a macro that transforms its humanly-readable input into something resembling the mess above.



(def token-type (token-generator)
(let tok (token-generator)
(if (is (tok 0 #\")) 'string
(is (tok 0 #\#) 'char)
(is-digit (tok 0) 'int))))


'let lets you pretend you are actually declaring a local variable. Coming from another language, you might think that this is, really and truly, a local variable. It's not. Behind the scenes, it's an anonymous function creating a lexically scoped 'tok parameter. But you don't care. You have your local variable.



Here is the pattern:



(let a b stuff)


becomes:



((fn (a) stuff) b)



'with is a more general purpose macro that does the same thing, but for multiple variables.



(with (a 1 b 2 c 3) stuff)


becomes:



((fn (a b c) stuff) 1 2 3)


'let exists because in the special case of binding just one variable, we don't need parens to separate the variable bindings from the body of the code. Just because you write in lisp doesn't mean you actually like parens ...

Thursday, June 5, 2008

To Do: If Only ...

One of the simplest and most pervasive macros in arc is "do". "do" is useful when you need to evaluate a sequence of expressions, but you only have room for one. A common case is when evaluating conditionals:



(if a b
c d
e)

This corresponds to the following in java:
if (a) { 
return b;
} else if (c) {
return d;
} else {
return e;
}


In java, we can execute as many statements as we desire within a { } block; arc however allows only a single expression as return value. So if we wanted



if (a) { 
b();
c();
return d;
} else if (e) {
return f;
} else {
return g;
}


we can't just write



(if a b c d
e f
g)


Hence, do.



(if a (do b c d)
e f
g)


do expands into a call to a no-arg function containing b c d -



((fn () b c d))


- it defines an anonymous zero-arg function and immediately calls it. Hence, b c d are grouped in a single expression, and will work as desired inside a conditional:



(if a ((fn () b c d))
e f
g)