ricky bobby's world
DESCRIPTION
JS Perf and Functional ProgrammingTRANSCRIPT
![Page 1: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/1.jpg)
Ricky Bobby’s worldA look into the js performance
![Page 2: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/2.jpg)
We’re speed freaks
![Page 3: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/3.jpg)
![Page 4: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/4.jpg)
![Page 5: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/5.jpg)
![Page 6: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/6.jpg)
![Page 7: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/7.jpg)
![Page 8: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/8.jpg)
![Page 9: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/9.jpg)
![Page 10: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/10.jpg)
![Page 11: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/11.jpg)
![Page 12: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/12.jpg)
![Page 13: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/13.jpg)
![Page 14: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/14.jpg)
![Page 15: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/15.jpg)
![Page 16: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/16.jpg)
![Page 17: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/17.jpg)
![Page 18: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/18.jpg)
![Page 19: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/19.jpg)
![Page 20: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/20.jpg)
[dahl]
![Page 21: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/21.jpg)
![Page 22: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/22.jpg)
![Page 23: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/23.jpg)
![Page 24: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/24.jpg)
Perf Paranoia
![Page 25: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/25.jpg)
![Page 26: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/26.jpg)
![Page 27: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/27.jpg)
![Page 28: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/28.jpg)
for loop vs foreach search
13,317,048
4,336,225
![Page 29: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/29.jpg)
for loop vs foreach search
13,317,048
4,336,225
![Page 30: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/30.jpg)
for loop vs foreach search
13,317,048
4,336,225
![Page 31: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/31.jpg)
![Page 32: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/32.jpg)
![Page 33: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/33.jpg)
![Page 34: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/34.jpg)
Speed Maintainability
![Page 35: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/35.jpg)
var result = []; for(var i=0, len=arr.length; i < len; i++) { result.push(doSomething(arr[i])); }return result;
For loops are faster!
arr.map(doSomething);
![Page 36: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/36.jpg)
MyFramework.CSSMyFramework.AnimationsMyFramework.HttpMyFramework.Socket
PROPERTY LOOKUPS
"Nesting objects in order to use dot notation is a great way to namespace and organize your code. Unfortunately, when it comes to performance, this can be a bit of a problem." -stoyan
http://www.phpied.com/extreme-javascript-optimization/
MyFramework.Rendering.CSSMyFramework.Rendering.AnimationsMyFramework.Network.HttpMyFramework.Network.Socket
![Page 37: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/37.jpg)
var Fn1 = 0;var Fn2 = 1;
function nextFibonacci() { var f1 = Fn1, f2 = Fn2, f3 = f1 + f2;
Fn1 = f2; Fn2 = f3; return f3;}
Global Variable Caching
var Fn1 = 0;var Fn2 = 1;
function nextFibonacci() { var ret = Fn1 + Fn2; Fn1 = Fn2; Fn2 = ret; return ret;}
![Page 38: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/38.jpg)
var iterations = Math.floor(values.length / 8);var leftover = values.length % 8;var i = 0;
if (leftover > 0){ do { process(values[i++]); } while (--leftover > 0);}
do { process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]); process(values[i++]);} while (--iterations > 0);
Loop unrolling
values.forEach(processData)
![Page 39: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/39.jpg)
This is a tiny town…
![Page 40: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/40.jpg)
![Page 41: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/41.jpg)
![Page 42: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/42.jpg)
![Page 43: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/43.jpg)
![Page 44: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/44.jpg)
![Page 45: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/45.jpg)
”you are not allowed to care about the performance of * unless you concatenate all your javascript, have it at the bottom, minify your css and js, gzip all your assets, and losslessly compress all your images. If you aren’t getting 90+ Page Speed scores, it’s way too early to be thinking about selector optimization.”
![Page 46: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/46.jpg)
![Page 47: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/47.jpg)
![Page 48: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/48.jpg)
Angels in the outfield
![Page 49: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/49.jpg)
JavaScript is a single threaded, interpreted
language
![Page 50: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/50.jpg)
![Page 51: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/51.jpg)
Meanwhile…
![Page 52: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/52.jpg)
![Page 53: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/53.jpg)
![Page 54: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/54.jpg)
![Page 55: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/55.jpg)
Enter the declarative
![Page 56: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/56.jpg)
![Page 57: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/57.jpg)
var result = []; for(var i=0, len=users.length; i < len; i++) { if(users[i].name.length > 0) { result.push(users[i]); } } return result;
users.filter(function(u){ return u.name.length > 0})
declarative
imperative
![Page 58: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/58.jpg)
59 // src :: FlickrItem -> URL 60 var src = compose(_.get('m'), _.get('media')); 61 62 // srcs :: FlickrSearch -> [URL] 63 var srcs = compose(map(src), _.get('items')); 64 65 // images :: FlickrSearch -> [DOM] 66 var images = compose(map(imageTag), srcs); 67 68 // tags :: FlickrSearch -> [DOM] 69 var tags = compose(toP, _.countBy(_.identity), _.filter(_.isEmpty), chain(split(' ' 70 71 // imagesAndTags :: Tuple [DOM] [DOM] 72 var imagesAndTags = liftA2(Tuple, images, tags) 73 74 // widget :: String -> PictureBox 75 var widget = compose(PictureBox, map(imagesAndTags), getJSON, url); 76 77 /////////////////////////////////////////////////////////////////////////////////// 78 79 mconcat([widget('cats'), widget('dogs')]).fork(log, function(x){ 80 compose(setHtml($('#flickr')), _.first)(x) 81 compose(setHtml($(‘#tag_cloud')), _.last)(x) 82 }); 83 });
![Page 59: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/59.jpg)
PART 2
![Page 60: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/60.jpg)
compose(map(g), map(f)) == map(compose(g, f))
chain(xs).map(f).map(g).value() == chain(xs).map(compose(g, f)).value()
Loop Fusion
![Page 61: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/61.jpg)
Loop Fusion
var bigFirst = compose(map(first), map(capitalize))
var bigFirst = map(compose(first, capitalize))
bigFirst(['ham', 'cake']) // ['H', 'C']
![Page 62: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/62.jpg)
Loop Fusion
var bigFirst = function(xs) { return chain(xs).map(capitalize).map(first).value()}
var bigFirst = function(xs) { return chain(xs).map(compose(first, capitalize)).value()}
bigFirst(['ham', 'cake']) // ['H', 'C']
![Page 63: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/63.jpg)
It works for Functors and Monads too!
![Page 64: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/64.jpg)
Shortcut Fusion
// g :: forall b. (t -> b -> b) -> b -> b
reduce(c, n, build(g)) = g(c, n)
![Page 65: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/65.jpg)
Shortcut Fusion
//build :: (forall b. (a -> b -> b) -> b -> b) -> [a]var build = function(g){ return g(concat, []);}
//+ map :: (a -> b) -> [a] -> [b]var map = curry(function(f, xs){ return build(function(c, n){ return reduce(function(acc, x){ return c(f(x), acc); }, n, xs); });});
![Page 66: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/66.jpg)
Shortcut Fusion
var sum = reduce(add, 0);
var sqr = function(x) {return x * x }
var sumSqs = compose(sum, map(sqr)) // reduce(function(acc, x){ return add(sqr(x), acc) }, 0);
![Page 67: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/67.jpg)
Shortcut Fusion
var sum = reduce(add, 0);
var sqr = function(x) {return x * x }
var sumSqs = function(xs) { return chain(xs).map(sqr).sum().value()// chain(xs).reduce(function(acc, x){ return add(sqr(x), acc) }, 0).value();}
![Page 68: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/68.jpg)
Good Producers
• List comprehensions
• concat
• foldr
• map
• take
• filter
• partition
• head
• and, or, any, all
• sequence
• sortBy
• zip
• zipWith
• List comprehensions
• Explicit lists
• cons
• concat
• map
• take
• filter
• iterate, repeat
• repeat
• zip
• zipWith
Good Consumers
![Page 69: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/69.jpg)
It works for Functors and Monads too!
![Page 70: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/70.jpg)
var addTwenty = memoize(function(x) {return x + 20;
})
Memoization
![Page 71: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/71.jpg)
var addTwenty = memoize(function(x) {return x + 20;
})
addTwenty(10) // 30addTwenty(10) // 30 (didn't run)addTwenty(11) // 31
Memoization
![Page 72: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/72.jpg)
Memoization
var getPosts = memoize(function(id) { return new Future(function(rej, res) { $.getJSON('/posts/'+id, res); });});
![Page 73: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/73.jpg)
Memoization
var getPosts = memoize(function(id) { return new Future(function(rej, res) { $.getJSON('/posts/'+id, res); });});
getPosts(2) // FuturegetPosts(2) // Future (didn't run)getPosts(3) // Future
![Page 74: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/74.jpg)
Memoization
var pQuery = $.toIO()
pQuery(".troll") // IO(function(){ return $(".troll") })
pQuery.runIO() // [Dom, Dom]pQuery.runIO() // [Dom, Dom, Dom]
![Page 75: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/75.jpg)
![Page 76: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/76.jpg)
It works for Functors and Monads too!
![Page 77: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/77.jpg)
Parallel code
f(f(x, y), z) == f(x, f(y, z))
![Page 78: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/78.jpg)
Parallel code
add(add(2, 3), 5) == add(2, add(3, 5))
![Page 79: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/79.jpg)
Parallel code
add(2, 3, 5)
![Page 80: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/80.jpg)
Parallel code
liftA3(fn, A1, A2, A3)
![Page 81: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/81.jpg)
Parallel code
var longCalc // Int -> Future(Int)
var collectResults = curry(function(rslt1, rslt2, rslt3){})
liftA3(collectResults, longCalc(20), longCalc(30), longCalc(10))
![Page 82: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/82.jpg)
Parallel code
var hasName // Attrs -> Validation
liftA3(save, hasName, hasEmail, hasPhone)
![Page 83: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/83.jpg)
It works for Functors and Monads too!
![Page 84: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/84.jpg)
Compile while youcompose
var characters = [ { 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }, { 'name': 'pebbles', 'age': 1 }];
var youngest = _.chain(characters) .sortBy('age') .map(function(chr) { return chr.name + ' is ' + chr.age; }) .first() .value();// → 'pebbles is 1’
![Page 85: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/85.jpg)
Compile while youcompose
var characters = [ { 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }, { 'name': 'pebbles', 'age': 1 }];
var youngest = _.chain(characters) .sortBy('age') .map(function(chr) { return chr.name + ' is ' + chr.age; }) .first() .value();// → 'pebbles is 1’
![Page 86: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/86.jpg)
Lazy code
![Page 87: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/87.jpg)
Lazy code
![Page 88: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/88.jpg)
It works for Functors and Monads too!
![Page 89: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/89.jpg)
![Page 90: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/90.jpg)
Just a handful
![Page 91: Ricky Bobby's World](https://reader036.vdocuments.site/reader036/viewer/2022081514/554954b6b4c905f74e8b4dc5/html5/thumbnails/91.jpg)
THANKS!@drboolean