EG Information
Training Missions
Knowledge Bank
|
|
Enigma Group's Articles
Closures in Lisp - Submitted By: ishkur88 2009-03-14 21:52:29
============================================================================ || Today we're going to be talking about lexical closures in Common Lisp. || ============================================================================
------------------------------ | What IS a lexical closure? | ------------------------------
A lexical closure is a function that captures free variables in an instance and is able to refer back to their address space at a later time.
--------------------------------- | What the hell does that mean? | ---------------------------------
Declaring free variables in a lexical scope would simply be defining a variable and then it's value in a single use context, such as in a LET statement:
; lexical scoping of the variable "a"
USER> (let ((a '(1 2 3))) (print a)) (1 2 3)
There you can see that "a" is defined inside of the LET statement and is then called to *standard-output* via the PRINT function.
---------------------------------- | So.. why is that useful to me? | ----------------------------------
At a glance, the ability to do that seems pointless. But, the truth of the matter is that it allows for some interesting things to be done in your scripts. Things that are downright impossible in a language that doesn't have support for closures.
--------------------------------------------------- | So what does one of these "closures" look like? | ---------------------------------------------------
A simple example of a closure would be a function that calls to a counter that displays a number and increases the value by a specified increment by one each time its called.
; simple example of a closure
(let ((calls 0)) (defun counter () (incf calls)))
What this does is create a local variable named "calls", then binds the function's use of it to its address space, allowing it to call that variable later.
You would then be able to call COUNTER as such:
USER> (counter) 1 USER> (counter) 2 USER> (counter) 3
and so on.
----------------------------------------------- | Well that's sorta neat, what else can I do? | -----------------------------------------------
You can actually define as many controlling functions as you need to in order to get the job done.
Example being:
(let ((calls 0)) (defun count-up () (incf calls)) (defun count-down () (decf calls)) (defun count-reset () (setq calls 0)))
With this, you can increase the count, decrease it, and start the count over.
----------------------------- | *Yawn* Ok, so is that it? | -----------------------------
Nope. Lets take a little detour into mathematics for a second.
There is a formula to directly calculate the nth Fibonacci term: F(n) = round( ( phi^n / sqrt( 5 ) ) and phi = ( 1 + sqrt( 5 ) ) / 2
Given that, we can write a nifty little closure to give us sequential terms of the fibonacci sequence:
(let ((term 0) (phi (/ (1+ (sqrt 5)) 2))) (defun next-fib () (progn (incf term) (round (/ (expt phi term) (sqrt 5))))))
Then it's just a matter of calling the function:
USER> (next-fib) 1 USER> (next-fib) 1 USER> (next-fib) 2 USER> (next-fib) 3
and so on.
----------------------------- | Ok, thats kinda neat too. | -----------------------------
Hell yeah it is. But there's just one problem with it. It only allows for one instance of the sequence to be used. What if, for some reason, we wanted two or more sequences running. What would we do then?
You would need to assign a name to each instance, and then pass the actions you'd like to do with it via function parameters.
Here's an example of how you could do something like this:
(defun fibonacci-sequence () (let ((term 0) (phi (/ (1+ (sqrt 5)) 2))) #'(lambda (operation) (ecase operation (next-fib (progn (incf term) (round (/ (expt phi term) (sqrt 5))))) (prev-fib (progn (decf term) (round (/ (expt phi term) (sqrt 5))))) (reset-fib (setq term 0))))))
What this is, is a factory for getting sequential terms of the Fibonacci sequence.
You would use it as such:
USER> (defparameter fib-terms-1 (fibonacci-sequence)) FIB-TERMS-1 USER> (defparameter fib-terms-2 (fibonacci-sequence)) FIB-TERMS-2 USER> (funcall fib-terms-1 'next-fib) 1 USER> (funcall fib-terms-1 'next-fib) 1 USER> (funcall fib-terms-1 'next-fib) 2 USER> (funcall fib-terms-2 'next-fib) 1 USER> (funcall fib-terms-1 'prev-fib) 1
and so on.
You are now able to create multiple named instances of this object.
------------------------------------ | Well.. ok. But how does it work? | ------------------------------------
The above script might look a little awkward to you, and that's alright. It is a litte weird, but it's nothing you can't understand.
What the LAMBDA expression does, is create a new address space for each new named instance by making an anonymous function.
ECASE is actually just a typical case switch like you'd see in any other language, except it has no default behavior. A given case has to match, or an error is signalled.
So, from there, you can see that a new set of local variables are defined every time a named instance, and allows calls to the enclosed functions.
------------ | Err.. Ok | ------------
If anything up there is a little unclear or you dont understand, dont bug out. I jumped into a semi-advanced topic without much warning.
Don't be afraid of the topic, and explore it as much as possible.
Happy Hacking. ~Ishkur Return to Programming category list
|
| |
|
|
Who's Online
388 Guests, 98 Users alexelixir, sharpestharp, alpineH@cker, BioHazard, alien007, f4nt0mx, Ausome1, themastersinner, Valterri, st3alth, thiscalling, FlamingLemming, psychomarine, ishkur88, chess_rock, teehee, nuxglwk, Psiber_Syn, Rik, crazyhacker54, enyo, upinsmoke, hawkcannon, jinx707, blink_212, NotMyOwn, Stumble, re6ter, vander130, Bengali, marv_92, R00tMANiAC, hawx_ps3, ruio, InjectioN, slyjakes, Red_beard, raj gupta, Nemehinmeli, Rex_Mundi, klesco, kazlu, geezer105, Blizer, th3punish3r, !~george~!, Ops, aVoid, it_trainee, Tony the Computer Guy, dasmasta, avalor, N4g4c3N, Nasrudin, aloksaini, TheRetech, exploit100, impairedyeti, gadjomatto, WhiteZ, jaiaccet, 0x80483fb, Posix, bharaniravikanth, Sloppypapi, ANdeRS06, c0zy, BlankBender, darkang3l, shogun, zinoustyle, arppan.009, dxmchurch1991, destroyer, raj.kiran.hero, nightstalker772, blackhat11907, JohnJohnJohn, beco_labise, bushranger, Abhinav2107, dtpvb2010, hackaday, zah2an, nocrew, ani_1, neofish, rad, m0ng00s3, norwood8, invas10n, dnatrixene135, Link-, Insp!red, xxreedxx222, samron7709, vkadarshvk, cutie117098 |
| |
|
|
|
|
|