« back

Let's Talk a Little Bit More About Lexical Scope

07 Jan 2013

Let's look at some other examples of lexical scope and how we can use that scope in bindings.

In clojure when dealing with functions you often times pass parameters. Those parameters have a lexical scope. They can only be seen inside the function body.

1 (defn example-function [example-parameter]
2     ;I can use example-parameter anywhere in example-function.
3 )
4 
5 (defn another-function [some-other-parameter]
6     ;I can use some-other-parameter anywhere in another-function.
7     ;if I try to use example-parameter here I will get an error since it is not in scope.
8 )

It is the same with ruby...

1 def example_method (example_parameter)
2     ;I can use example_parameter anywhere in example_method.
3 end
4 
5 def another_method(some_other_parameter)
6     ;I can use some_other_parameter anywhere in another_method.
7     ;if I try to use example_parameter here I will get an error since it is not in scope.
8 end

Something that you may have seen in both languages is the use of let. Let's(haha) look at the use of let in clojure first. The syntax looks like this...

1     (let [bindings*] exprs*)

Then, the value of the let will be the value of the last expression in exprs*. The bindings will persist anywhere in the let block. For example, we can take a look at some code that I wrote for my command parser.

1 (defn decode-structured-messages
2 	([input] (decode-structured-message input plain-text))
3 	([input decoder]
4 	(let [result (map (fn [structured-message] (decode-structured-message structured-message decoder )) (str/split input #";"))]
5 		(doall result))))

As you can see, we can use let and bind result to what is returned from (map (fn [structured-message] (decode-structured-message structured-message decoder )) (str/split input #";")). The last evaluated expression in the let is (doall result), so that is the value that will be returned as the value of the let.

Now we can take a look at let in Rspec. In Rspec, you can use let() to help DRY up your code. You can choose to use let over a before since let loads lazily(when you need it and then remains cached for the rest of the example that you are currently in.) You use let inside of a describe block. It was made that way and is not valid if you try to use it outside. That is interesting because the describe block creates a wrapper that allows the value of let to remain in scope within the describe block and therefore the examples as well.

 1 describe "explaining let"
 2 
 3     let (:test_1) { 1 }
 4     let (:test_2) { 1 + 1 }
 5     let (:tc)     { TestClass.new }
 6 
 7     it "should allow me to assign the value of let"
 8         puts test_1
 9         puts test_2
10     end
11 => 1
12    2
13 end

Essentially, let allows you to bind the expression in the block to the symbol in the (). This then creates a method that you can use to call when you are writing your examples. Just like with the clojure example, the evaluation of the expression is returned from the let. When we let (:test_2) to { 1 + 1 }, the value returned is 2.(the evaluation of the expression.) This is really cool. You likely most use or see it like the third example let (:tc) { TestClass.new } but you can use it for more than just that.

If you ask me, that is pretty sweet!

comments powered by Disqus