Skip to content

Latest commit

 

History

History
287 lines (213 loc) · 8.45 KB

45closures.md

File metadata and controls

287 lines (213 loc) · 8.45 KB

    home | syllabus | submit | chat

    © 2017 tim@menzies.us


Stateful Closures

(from http://nathansjslessons.appspot.com/lesson?id=1070)

A useful thing that closures can do is keep track of internal state. A closure is created when:

  • The scope of some inner function refers to an outer scope's variables.
  • The inner function is passed around as a variable, carrying with it the inner scope and the outer scope.

For example, here's a function that uses closures to create a counter.

var makeCounter = function () {
    var count, f;
    count = 0;
    f = function () {
        count = count + 1;
        return count;
    };
    return f;
};
var counter = makeCounter();
/* counter is a function that takes no arguments
   and returns a count */
 
var a = counter(); // gets 1
var b = counter(); // gets 2
var c = counter(); // gets 3
 
var counter2 = makeCounter(); // create another one
var d = counter2(); // gets 1
var e = counter2(); // gets 2
var f = counter(); // gets 4 from first one

Where is the state kept? It's in the variable count in the outer scope. Why are there two copies? Each invocation of makeCounter creates a new scope for count, and the closure surrounding the inner function f captures that scope.

Applications to SE

In the next para, objects DRY, layers, 3-tierd architecture, the 3.5 rule

Cool Programming

In LISP, one (and only one) recursive data type, the list which contains either atoms or another list

e.g. visitor pattern in functional (5 lines)

(defun visitr (things f)
  "visitor patterns in functional programming"
  (if (atom things)
      (funcall f things)   ; then
      (dolist (one things) ; else do for each
             (visitr one f))))

(defun demo (&aux all)
  (let ((nastyComplexThing
         '(a
           (b 1)
           c
           (d e
              (f g
                 (2 3 4)
                 )
              h)
           (i j)
           (k
            (l
             (m
              (n o p q r
                 (s 5 6 7)
                 (t 8 9 )
                 u v w x y z)))))))
    (visitr nastyComplexThing
            (lambda (x)
              (if (numberp x)
                  (push x all))))
    all))

(print(demo))

; output
; (9 8 7 6 5 4 3 2 1)

Parallelism

Pure functions return values that are determined only by their inputs (no reference to globals):

   x = plus(y,x)

So the whole execution of plus can run in its own separate environment.
Enter closures .Bundle all you need into closures, call n functions, each running on its own CPU.

Packages

Internal details hidden from the user of the package. Just use the published

In a language called Lua,

do
    local function checkComplex (c) # function local to inside this do-end block
      if not ((type(c) == "table")
         and tonumber(c.r) and tonumber(c.i)) then
        error("bad complex number", 3)
      end
    end
    
    local function new (r, i) return {r=r, i=i} end
    
    local function add (c1, c2)
      checkComplex(c1);
      checkComplex(c2);
      return new(c1.r + c2.r, c1.i + c2.i)
    end
    
    #other functions here
    
    complex = {  # when not marked "local", defaults to global
      new = new,
      add = add,
      sub = sub,
      mul = mul,
      div = div,
    }
end

Note that checkComplex is not in the public interface.

Oh, and to call it:

require "complex"
C= complex
i = C.new(2,2)
j = C.new(10,0)
k = C.add(i,j)

Objects, Classes

Once you've got closures and packages, you are nearly all the way to an OO language

Applications of Closures: a 3-tiered architecture:

  • Data : persistence storage; e.g. some SQL tools
  • Model : the semantics; e.g. objects for Person, Employee, Account, with methods like promote, hire, etc
  • Dialag: user interaction control; e.g. Employee Editor

Layers allows for:

  • faster development (build the Model, then reuse someone's database layer)
  • portability can take the same model and port it to different platforms (different database or different GUIs on different platforms)

For years, this 3-tiered architecture was the standard architecture and we did estimation as follows:

  • Design the "N" classes in the model
  • Assume total number of classes is N*3.5 (each model class gets one friend in the database and dialog layer plus maybe some general utilities (half a class).
  • Assume 20 lines per method and 5 methods per class.
  • LOC = N3.520*5 = 350 lines*N model classes
  • Track LOC per month with your local developers (including testing)
  • Effort = LOC per month * LOC = effort in months + Assume half that time is in testing (developers' local tests and integrating their code with other people)

More generally, think of the interactions between buttons pressed in the Dialog layer and the Model layer (all these services in the Dialog need Model information).

E.g. where to define, say the range of legal values for age of an employee?

  • In Person (0 to 120) in the model layer?
  • In PersonEditor in the dialog layer?
  • As a trigger on updates in the database layer?

E.g. when you hit "ok" on the Employee Editor:

  • You can't close the window till all the constraints check out;
  • After close, you have to save the Employee... Dialog updates the list of Employees in the Model layer and maybe even update the Employee in the database layer.

So the "layers" aren't really separated: so much interaction

Solutions

Rails, DRY

DRY= Don't Repeat Yourself.

Define it once, auto-propagate to the rest of the system.

Write the model, the auto-generate most of the Data and Dialog layer

  • E.g. auto write the triggers on the database layers
  • E.g. auto create on one database table for each business model.
  • E.g. auto create on editor for each model object.

Closures

The model generates closures (functions to that, e.g. return False if the some contents and not valid w.r.t. to model constraints).

At the Dialog layer, buttons have "call backs" ; closures generated by Model objects and cached in Dialog layer.

Note that the Dialog does not need to know much about the model

  • Just, when contents change, as the constraints closure if it returns "True".

Closure and Clojure

Closures should not be confused with Clojure.

  • Closures are a general programming construct that are used in many languages.
  • Clojure is a modern variant of Lisp that supports closures and concurrency via closures.

Clojure supports immutable variables-- variables which can only ever be written once and once only. This seems like a strange idea- but it actually simplifies debugging and concurrency (since once a variable gets a value, it is a stable binding for the life of that variable).

To get a feel for how immutable variables and recursion changes programming, consider the following standard example of iteration. Note that is uses mutable variables (ones that can be updated).

double result = 1.0;
for (int i = 0; i < y; i++)
{
  result = result * x;
}

Here's the equivalent call in Clojure. Note that we do not reset any variable, we just create news one in some nested environment:

(defn power
  ([x y] (power x y 1))
  ([x y current]
  (if (= y 0)
    current
    (if (> y 0)
      (recur x (- y 1) (* x current))
      (recur x (+ y 1) (/ current x))))))

Immutable variables and programming-by-recursion are widely-used techniques in functional programming. In the older languages, they are depreciated since recursive calls to functions become stack of recursive calls that eat up memory. However, for languages that support tail recursive optimization (like the we can replace the recursive call with a loop back to the same function call on the current stack (caveat: the tail recursive call has to be the last thing down in the function; i.e. we do not need any space of additional variables after that final recursive call).

Note that Clojure supports tail recursive optimization, sort of. Clojure compiles into the Java Virtual Machine where such optimizations are... complicated, which Clojure finesses uses certain technical tricks see here for details.