On Github atuttle / preso-closures
@AdamTuttle
May 3rd, 2014
When I'm not coding or speaking, I like to jump out of airplanes.$.get( 'http://api.openweathermap.org/data/2.5/weather?q=Las%20Vegas,%20NV' , function(data){ console.log(data) } );
var fs = require('fs'); fs.readFile('data.txt', function(err, data){ if (err){ console.log('there was an error'); console.error(err); return; } console.log('file read complete'); console.log(data); });
<cfscript> function myCallback(){ return "called back!"; } function do_something_and_call_back( cb ){ cb(); } </cfscript> <cfoutput>#do_something_and_call_back( myCallback )#</cfoutput>- When CF added UDFs in CF5, this syntax became possible - Currently not possible to use a built-in function as a callback (e.g. WriteOutput) - It's rare to see this in CF code: callbacks are typically a sign of async... CFML is synchronous
ArrayEach([1,2,3], function(item){ writeOutput( item ); });
A closure is a function-object
-- a function that can be passed as anargument to another function --
that always remembers its context
All functions are closures*
- In languages that support closures, all UDFs are closures. - Function object = UDF - Context? We'll talk about that in a bit.CF has supported callbacks since CF5.
CF10 added closure support.
component hint="not a closure, JUST A METAPHOR!" { variables = { ... }; function execute(){ ... } }- This CFML object is not a closure, but it should give you an idea of what a closure is. - It's an object: you can pass it as an argument to another function - It's a function: you can execute it - Whatever the variables were when it was created, they will remain (except globals)
function times10(n){ return n * 10; } tens = [1,2,3,4,5].map( times10 );
Result:
=> tens: [10,20,30,40,50]- CF doesn't have a map function like this until CF11 (more later)
function times(multiplier){ return function(n){ return multiplier * n; }; } tens = [1,2,3,4,5].map( times(10) ); hundreds = [1,2,3,4,5].map( times(100) );
Result:
=> tens: [10,20,30,40,50] => hundreds: [100,200,300,400,500]- Here we're using closures to get rid of the hard-coded multiplyer of 10
function defer(job, onSuccess, onFailure, onTerminate){ var threadName = CreateUUID(); cfthread.status = "Running"; thread name="#threadName#" action="run" attributecollection=arguments { try { successData.result = job(); cfthread.status = "Completed"; onSuccess(successData); } catch (any e){ cfthread.status = "Failed"; onFailure(e); } } return { getStatus = function(){ return cfthread.status; } ,getThreadName = function(){ return threadName; } ,terminate = function(){ if (cfthread.status != "Running"){ return; } thread name="deferThread" action="terminate"; cfthread.status = "Terminated"; onTerminate(); } }; }
function matches(str){ return function(item){ return (str == item); }; }- When I was first learning about closures, this drove me nuts because I HATE unscoped variable references - Could we write return (arguments.str == arguments.item)? NO! - Thus, you must have at least a basic understanding of...
The Scope Chain is the list of all scopes that ColdFusion will check when you make an un-scoped variable reference.
return name; //uses scope chain return arguments.name; //does not use scope chain- Socpe Chain is a Personal Pet Peeve of mine - 2007: Scope Nazi ~ bugs caused by not understanding scope chain. I had no idea how right I was. - 2009: Scope Priority Changes in ColdFusion 9.
See anything missing?
- Application, Session, Request, This, and Server must always be explicitly referenced - Adam Cameron: Listen all coders: quit vilifying Cold Fusion's useless features: CFForm, CFPodfunction callMe( name ) { return "Hi, #name#."; } callMe( 'maybe' );
When we run this code, what is returned?
Hi, maybe.
function callMe( name ) local.name = "Uhura"; local.q = queryNew("name", "varchar", [{ name: "Spock" }]); output query="local.q" { return name; } } variables.person = callMe( "Kirk" );
What's the value of variables.person?
- Showing some new syntax from CF11: output{} - previously impossible to have a query-scoping loop in scriptAdobe ColdFusion 10+: Kirk (Arguments)Railo: Uhura (Local)
- Railo refuses to fix this because ACF local scope includes arguments scopecomponent { variables.FOO = 5; function closure_creator( FOO = 4 ){ local.FOO = 3; return function( FOO = 2 ){ local.FOO = 1; return FOO; } } }- This is a visual map of the schope chain. - The integer values here are in the order that Railo will look for them. Notice how they start at the inner-most line and work their way out. - Personally I feel this makes more sense than ACF.
component { variables.FOO = 5; function closure_creator( FOO = 3 ){ local.FOO = 4; return function( FOO = 1 ){ local.FOO = 2; return FOO; } } }- Note that the local variables and arguments order is swapped in both the inner closure as well as the function that's returning the closure.
The this scope is not in the scope chain.
Component this scope.
- If you reference this then you'll get the component's THIS scope, if there is one availablearrayEach( [1,2,3,4,5,6,7], function( item ){ writeOutput( dayOfWeekAsString( item ) & "<br/>" ); });
Prints:
Sunday Monday Tuesday Wednesday Thursday Friday Saturday
structEach({ one:1, two:2 }, function(key, value){ writeOutput( key & ": " & value ); });
Prints:
ONE: 1 TWO: 2- "...EACH" functions give you the opportunity to use every item in a collection
filtered = ArrayFilter(["cf.Objective()","NCDevCon"], function(item){ return len(item) > 8; });
Resulting Array:
=> [1]- Instead of using each item, now we filter based on the result of a truth-test callback - See anything wrong here? Instead of getting the item back, we get its index. (LAME!)
data = [{ age: 5 },{ age: 15 }]; filtered = ArrayFindAll(data, function(item){ return item.age < 10; });
Resulting Array:
=> [1]- Again we only get the indexes of the items that pass the truth test - In this form, same as ArrayFilter: truth test callback - don't be fooled by Array Find All No Case!
data = ["Adam","ADAM","adam","aDaM"]; filtered = ArrayFindAll(data, "Adam"); => [1] filtered = ArrayFindAllNoCase(data, "Adam"); => [1,2,3,4]- Undocumented! - This is the reason I put an asterisk next to them - ONLY place that the "no case" part of the name applies - not with callback
Combine values in some meaningful way
complexData = [ {a: 4}, {a: 18}, {a: 51} ]; function reducer(prev, element){ return prev + element.a; } sum = arrayReduce( complexData, reducer, 0 ); => 73- This is flattened out to make it easier to read and to see the initial value
complexData = [ {a: 4}, {a: 18}, {a: 51} ]; sum = arrayReduce( complexData, function(prev, element){ return prev + element.a; }, 0 ); => 73
complexData = [ {a: 4}, {a: 18}, {a: 51} ]; newArray = arrayMap( complexData, function(item){ return item.a; }, 0 ); => [4, 18, 51]
None of those things require the use of closures.
Here's something that does
- Jumping from anonymous function usage to real closures does take the complexity up a notch, so pay close attention.function curry(func, args){ var argMap = {}; var counter = 1; ArrayEach(args, function(it) { argMap[counter++] = it; }); return function(){ var newArgs = StructCopy(argMap); var len = StructCount(argMap); StructEach(arguments, function(key, value){ newArgs[len + key] = value; }); return func(argumentCollection=newArgs); }; }- Copied and condensed from Mark Mandel's Sesame project - No need to grok how it works, just showing it returns a function (a closure)
Using Curry:
function orig(a,b,c,d){ return a & " " & b & " " & c & " " & d; } hi_there = curry( orig, ['hi', 'there'] ); greeting = hi_there( 'cf.Objective()', 'attendees' );
Returns:
=> greeting: "hi there cf.Objective() attendees"
CF10:
var NAMES = arrayEach(['Stroz','Ferguson','Cunningham'], function(i){ return ucase(i); });
CF11:
var NAMES = arrayEach(['Stroz','Ferguson','Cunningham'], ucase);
By Mark Mandel
By Russ Spivey
Mostly-complete port of underscore.js
$.get('http://www.google.com', function(data){ $( '#myDiv' ).html( data ); });- jQuery is more than just a FP lib, but almost everything you do with it uses FP style - Often use of closures adds even more awesome to jQ
Prefix array items:
function getGreetings( people ){ var prefix = 'hi, '; var greetings = _.map(people, function( item ){ return prefix + item; }); return greetings; } console.log( getGreetings( ['mom','dad'] ) );
Logs:
=> [ 'hi, mom', 'hi, dad' ]- JS Community standard for FP utility functions - Most depended on module in NPM, by almost 1k (25%)
var sum = _.reduce([1,2,3,4,5,6], function( memo, num ){ return memo + num; }, 0); console.log( sum );
Logs:
=> 21
var scrollListener = _.debounce(function(){ console.log( document.body.scrollTop ); }, 250); $( window ).on( 'scroll', scrollListener );- Scroll is a noisy event. Only run when it's been >= 250ms since the last run.
data = [ { id: 'name', value: 'Adam Tuttle' } ,{ id: 'age', value: 32 } ,{ id: 'pets', value: 2 } ]; _.each(data, function(el, ix, arr){ $('#' + el.id).val(el.value); });- Pre-filling a form from an ajax response
component { variables.A = 5; function closure_creator( A = 3 ){ local.A = 4; return function( A = 1 ){ local.A = 2; return A; } } }
github.com/atuttle/preso-closures
Please fill out session surveys!