_.m is a port of Underscore.js to Objective-C. It strives to provide the fullest feature set possible in a way that is familiar to JavaScript developers (despite the differences between JavaScript and Objective-C).
To help achieve this vision, _.m uses SubjectiveScript.m to bring JavaScript-like syntax and features into Objective-C, and QUnit.m to port unit tests from JavaScript to Objective-C. You should check them out, too!
platform :ios pod '_.m', '~> 0.1.0' pod 'SubjectiveScript.m', '~> 0.1.0'
pod install {YourProject}.xcodeproj{YourProject}.xcworkspace file instead of {YourProject}.xcodeprojSubjective-Script helps get rid of [tooMany squareBrackets], compresses construction of objects and numbers, and implements a JavaScript-like syntax and feature set. Check out the GitHub Page for more details. An example....
_.compact([NSArray arrayWithObjects:[NSNumber numberWithInteger:0], [NSNumber numberWithInteger:1], [NSNumber numberWithInteger:2], nil]); // [1,2]
_.compact(AI(0, 1, 2)); // [1,2]
Note: Most of the following examples of Underscore.js are directly from the Underscore.js website to ease understanding.
_.each(list, iterator, [context])docs
_.each([1, 2, 3], function(num){ alert(num); });
=> alerts each number in turn...
_.each({one : 1, two : 2, three : 3}, function(num, key){ alert(num); });
=> alerts each number in turn...Arg: _EachBlock => ^(id value, ...) ... => id key, id list
_.each(id list, _EachBlock iterator) no context
_.each(AI(1, 2, 3), ^(N* num, ...){ SS.alert(num); });
=> alerts each number in turn...
_.each(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}), ^(N* num, ...){ SS.alert(num); });
=> alerts each number in turn...
eachWithStopReturns: voidArg: _EachWithStopBlock => ^B(id value, ...) ... => id key, id list
_.eachWithStop(id list, _EachWithStopBlock iterator) specialized
_.eachWithStop(AI(1, 2, 3), ^B(N* num, ...){ SS.alert(num); return (num.I<3); });
=> alerts each number under 3 in turn...
_.map(list, iterator, [context])docs
_.map([1, 2, 3], function(num){ return num * 3; });
=> [3, 6, 9]
_.map({one : 1, two : 2, three : 3}, function(num, key){ return num * 3; });
=> [3, 6, 9]Arg: _MapBlock => ^id(id value, ...) ... => id key, id list
_.map(id list, _MapBlock iterator) no context
_.map(AI(1, 2, 3), ^(N* num, ...){ return N.I(num.I * 3); });
=> [3, 6, 9]
_.map(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}), ^id(N* num, ...){ return N.I(num.I * 3); });
=> [3, 6, 9]
_.reduce(list, iterator, memo, [context])docs
var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
=> 6Arg: _ReduceBlock => ^id(id memo, id value, ...) ... => id key, id list
_.reduce(id list, _ReduceBlock iterator, id memo) no context
N* sum = _.reduce(AI(1, 2, 3), ^(N* memo, N* num, ...){ return N.I(memo.I + num.I); }, N.I(0));
=> 6
_.reduceRight(list, iterator, memo, [context])docs
var list = [[0, 1], [2, 3], [4, 5]];
var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []);
=> [4, 5, 2, 3, 0, 1]Arg: _ReduceBlock => ^id(id memo, id value, ...) ... => id key, id list
_.reduceRight(id list, _ReduceBlock iterator, id memo) no context
A* list = AO(AI(0, 1), AI(2, 3), AI(4, 5));
A* flat = _.reduceRight(list, ^(A* a, A* b, ...) { return a.concat(b); }, A.new);
=> [4, 5, 2, 3, 0, 1]
_.find(list, iterator, [context])docs
var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> 2_.find(id list, _FindBlock iterator) no context
N* even = _.find(AI(1, 2, 3, 4, 5, 6), ^B(N* num){ return num.I % 2 == 0; });
=> 2
_.filter(list, iterator, [context])docs
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.filter(id list, _ItemTestBlock iterator) no context
A* evens = _.filter(AI(1, 2, 3, 4, 5, 6), ^B(N* num, ...){ return num.I % 2 == 0; });
=> [2, 4, 6]
_.reject(list, iterator, [context])docs
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [1, 3, 5]Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.reject(id list, _ItemTestBlock iterator) no context
A* odds = _.reject(AI(1, 2, 3, 4, 5, 6), ^B(N* num, ...){ return num.I % 2 == 0; });
=> [1, 3, 5]
_.all(list, iterator, [context])docs
_.all([true, 1, null, 'yes'], _.identity); => false
Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.all(id list, _ItemTestBlock iterator) no context
_.all(AO(N.B(true), N.I(1), nil, @"yes"), _.identityTruthy) => false
Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.any(id list, _ItemTestBlock iterator) no context
_.any(AO(nil, N.I(0), @"yes", N.B(false)), nil); => true
_.include(id list, id target)
_.include(AI(1, 2, 3), N.I(3)); => true
_.invoke(list, methodName, [*arguments])docs
_.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); => [[1, 5, 7], [1, 2, 3]]
_.invoke(id list, NSS* methodName, id arg1, ...) nil terminated
_.invoke(AO(AI(5, 1, 7), AI(3, 2, 1)), @"sort", nil); => [[1, 5, 7], [1, 2, 3]]
_.pluck(list, propertyName)docs
var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
_.pluck(stooges, 'name');
=> ["moe", "larry", "curly"]_.pluck(id list, NSString *propertyName)
A* stooges = AO(OKV({@"name", @"moe"}, {@"age", N.I(40)}), OKV({@"name", @"larry"}, {@"age", N.I(50)}), OKV({@"name", @"curly"}, {@"age", N.I(60)}));
_.pluck(stooges, @"name");
=> ["moe", "larry", "curly"]
_.max(list, [iterator], [context])docs
var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
_.max(stooges, function(stooge){ return stooge.age; });
=> {name : 'curly', age : 60};_.max(id list, _MaxBlock iterator)
A* stooges = AO(OKV({@"name", @"moe"}, {@"age", N.I(40)}), OKV({@"name", @"larry"}, {@"age", N.I(50)}), OKV({@"name", @"curly"}, {@"age", N.I(60)}));
// you can use stooge.age if you use SubjectiveScript's NamedProperties
_.max(stooges, ^(O* stooge){ return stooge.get(@"age"); });
=> {name : 'curly', age : 60};
_.min(list, [iterator], [context])docs
var numbers = [10, 5, 100, 2, 1000]; _.min(numbers); => 2
_.min(id list, _MinBlock iterator)
A* numbers = AI(10, 5, 100, 2, 1000); _.min(numbers, nil); => 2
_.sortBy(list, iterator, [context])docs
_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
=> [5, 4, 6, 3, 1, 2]Arg: _SortByBlock => ^id(id value, ...) ... => id key, id list
_.sortBy(id list, id iteratorOrKey) _SortByBlock or id
no context
_.sortBy(AI(1, 2, 3, 4, 5, 6), ^(N* num, ...){ return N.F(sin(num.I)); });
=> [5, 4, 6, 3, 1, 2]
_.groupBy(list, iterator)docs
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); });
=> {1: [1.3], 2: [2.1, 2.4]}
_.groupBy(['one', 'two', 'three'], 'length');
=> {3: ["one", "two"], 5: ["three"]}Arg: _GroupByBlock => ^id(id value, ...) ... => id key, id list
_.groupBy(id list, id iteratorOrKey) _GroupByBlock or id
no context
_.groupBy(AF(1.3, 2.1, 2.4), ^(N* num, ...){ return N.I(floor(num.F)); });
=> {1: [1.3], 2: [2.1, 2.4]}
_.groupBy(AO(@"one", @"two", @"three"), @"length");
=> {3: ["one", "two"], 5: ["three"]}
_.sortedIndex(list, value, [iterator])docs
_.sortedIndex([10, 20, 30, 40, 50], 35); => 3
Arg: _SortedIndexBlock => ^id(id value, ...) ... => id key, id list
_.sortedIndex(NSA* array, id list, _SortedIndexBlock iterator)
_.sortedIndex(AI(10, 20, 30, 40, 50), N.I(35), nil); => 3
_.shuffle(list)docs
_.shuffle([1, 2, 3, 4, 5, 6]); => [4, 1, 6, 3, 5, 2]
_.shuffle(id list)
_.shuffle(AI(1, 2, 3, 4, 5, 6)); => [4, 1, 6, 3, 5, 2]
_.toArray(list)docs
(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
=> [2, 3, 4]_.toArray(id list)
(^(I arg1, ...){ ARGS_AI(arguments, arg1); return _.toArray(arguments).slice(1, 0); })(1, 2, 3, 4, AI_END);
=> [2, 3, 4]
_.size(list)docs
_.size({one : 1, two : 2, three : 3});
=> 3_.size(id list)
_.size(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}));
=> 3
_.first(NSA* array, I n) n<0 for optional
_.first(AI(5, 4, 3, 2, 1), -1); => 5firstIteratorReturns: NSO*
_.firstIterator(NSA* array, ...) specialized ... => id key, id list
_.map(AO(AI(2, 1), AI(3, 1)), _.firstIterator); => [2, 3]
_.initial(array, [n])docs
_.initial([5, 4, 3, 2, 1]); => [5, 4, 3, 2]
_.initial(NSA* array, I n) n<0 for optional
_.initial(AI(5, 4, 3, 2, 1), -1); => [5, 4, 3, 2]initialIteratorReturns: NSA*
_.initialIterator(NSA* array, ...) specialized ... => id key, id list
_.map(AO(AI(2, 1, 3), AI(3, 1, 2)), _.initialIterator); => [[2, 1], [3, 1]]
_.last(array, [n])docs
_.last([5, 4, 3, 2, 1]); => 1
_.last(NSA* array, I n) n<0 for optional
_.last(AI(5, 4, 3, 2, 1), -1); => 1lastIteratorReturns: NSO*
_.lastIterator(NSA* array, ...) specialized ... => id key, id list
_.map(AO(AI(2, 1), AI(3, 1)), _.lastIterator); => [1, 1]
_.rest(NSA* array, I index) n<0 for optional
_.rest(AI(5, 4, 3, 2, 1), -1); => [4, 3, 2, 1]restIteratorReturns: NSA*
_.restIterator(NSA* array, ...) specialized ... => id key, id list
_.map(AO(AI(2, 1, 3), AI(3, 1, 2)), _.restIterator); => [[1, 3], [1, 2]]
_.compact(array)docs
_.compact([0, 1, false, 2, '', 3]); => [1, 2, 3]
_.compact(NSA* array)
_.compact(AO(N.I(0), N.I(1), N.B(false), N.I(2), @"", N.I(3))); => [1, 2, 3]
_.flatten(array, [shallow])docs
_.flatten([1, [2], [3, [[4]]]]); => [1, 2, 3, 4]; _.flatten([1, [2], [3, [[4]]]], true); => [1, 2, 3, [[4]]];
_.flatten(NSA* array, B shallow)
_.flatten(AO(N.I(1), AI(2), AO(N.I(3), AO(AI(4)))), false); => [1, 2, 3, 4]; _.flatten(AO(N.I(1), AI(2), AO(N.I(3), AO(AI(4)))), true); => [1, 2, 3, [[4]]];
_.without(array, [*values])docs
_.without([1, 2, 1, 0, 3, 1, 4], 0, 1); => [2, 3, 4]
_.without(NSA* array, id value1, ...) nil terminated
_.without(AI(1, 2, 1, 0, 3, 1, 4), N.I(0), N.I(1), nil); => [2, 3, 4]
_.union(*arrays)docs
_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); => [1, 2, 3, 101, 10]
_.union_(NSA* array, ...) nil terminated reserved => union_
_.union_(AI(1, 2, 3), AI(101, 2, 1, 10), AI(2, 1), nil); => [1, 2, 3, 101, 10]
_.intersection(*arrays)docs
_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); => [1, 2]
_.intersection(NSA* array, NSA* array1, ...) nil terminated
_.intersection(AI(1, 2, 3), AI(101, 2, 1, 10), AI(2, 1), nil); => [1, 2]
_.difference(array, *others)docs
_.difference([1, 2, 3, 4, 5], [5, 2, 10]); => [1, 3, 4]
_.difference(NSA* array, NSA* other1, ...) nil terminated
_.difference(AI(1, 2, 3, 4, 5), AI(5, 2, 10), nil); => [1, 3, 4]
_.uniq(array, [isSorted], [iterator])docs
_.uniq([1, 2, 1, 3, 1, 4]); => [1, 2, 3, 4]
_.uniq(NSA* array)
_.uniq(AI(1, 2, 1, 3, 1, 4)); => [1, 2, 3, 4]uniqAdvancedAlias: uniqueAdvancedReturns: A*
Arg: _MapBlock => ^id(id value, ...) ... => id key, id list
_.uniqAdvanced(NSA* array, B isSorted, _MapBlock iterator) specialized
_.uniqAdvanced(AI(1, 2, 2, 3, 4, 4), true, ^(N* value, ...) { return N.I(value.I + 1); });
=> [1, 2, 3, 4]
_.zip(*arrays)docs
_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); => [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
_.zip(NSA* array, ...) nil terminated
_.zip(AO(@"moe", @"larry", @"curly"), AI(30, 40, 50), AB(true, false, false), nil); => [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
_.zipObject(keys, values)
_.zipObject(['moe', 'larry', 'curly], [30, 40, 50]);
=> {moe:30,larry:40,curly:50}_.zipObject((NSA* keys, NSA* values)
_.zipObject(AO(@"moe", @"larry", @"curly"), AI(30, 40, 50));
=> {moe:30,larry:40,curly:50}
_.indexOf(array, value, [isSorted])docs
_.indexOf([1, 2, 3], 2); => 1
_.indexOf(NSA* array, id value)
_.indexOf(AI(1, 2, 3), N.I(2)); => 1indexOfSortedReturns: I
_.indexOfSorted(NSA* array, id value) specialized
_.indexOfSorted(AI(10, 20, 30, 40, 50), N.I(30)); => 2
_.lastIndexOf(array, value)docs
_.lastIndexOf([1, 2, 3, 1, 2, 3], 2); => 4
_.lastIndexOf(NSA* array, id value)
_.lastIndexOf(AI(1, 2, 3, 1, 2, 3), N.I(2)); => 4
_.range([start], stop, [step])docs
_.range(10); => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] _.range(1, 11); => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _.range(0, 30, 5); => [0, 5, 10, 15, 20, 25] _.range(0, -10, -1); => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] _.range(0); => []
_.range(I start, I stop, I step)
_.range(1, 11, 1); => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _.range(0, 30, 5); => [0, 5, 10, 15, 20, 25] _.range(0, -10, -1); => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]rangeSimpleReturns: A*
_.rangeSimple(I stop) specialized
_.rangeSimple(10); => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] _.rangeSimple(0); => []
_.bind(function, object, [*arguments])docs
var func = function(greeting){ return greeting + ': ' + this.name };
func = _.bind(func, {name : 'moe'}, 'hi');
func();
=> 'hi: moe'XJavaScript only
_.bindAll(object, [*methodNames])docs
var buttonView = {
label : 'underscore',
onClick : function(){ alert('clicked: ' + this.label); },
onHover : function(){ console.log('hovering: ' + this.label); }
};
_.bindAll(buttonView);
jQuery('#underscore_button').bind('click', buttonView.onClick);
=> When the button is clicked, this.label will have the correct value...XJavaScript only
_.memoize(function, [hashFunction])docs
var fibonacci = _.memoize(function(n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
});Arg: _MemoizeBlock => ^id(id arg1, ...) nil terminated
Arg: _MemoizeHashBlock => ^id(id arg1, ...) nil terminated_.memoize(_MemoizeBlock func, _MemoizeHashBlock hasher)
__block _MemoizedBlock fibonacci = _.memoize(^N*(N* n, ...) {
return n.I < 2 ? n : N.I(fibonacci(N.I(n.I - 1), nil).I + fibonacci(N.I(n.I - 2), nil).I);
}, nil);
_.delay(function, wait, [*arguments])docs
var log = _.bind(console.log, console); _.delay(log, 1000, 'logged later'); => 'logged later' // Appears after one second.
_.delay(_DelayBlock func, I waitNS) no arguments
_.delay(^{ NSLog(@"%d", 1000); }, NSEC_PER_SEC*1);
=> 'logged later' // Appears after one second.delayBackgroundReturns: voidArg: _DelayBlock => ^()_.delayBackground(_DelayBlock func, I waitNS) added
_.delayBackground(^{ NSLog(@"%d", 1000); }, NSEC_PER_SEC*1);
=> 'logged later' // Appears after one second.
_.defer(function, [*arguments])docs
_.defer(function(){ alert('deferred'); });
// Returns from the function before the alert runs._.defer(_DeferBlock func) no arguments
_.defer(^{ SS.alert(@"deferred"); });
// Returns from the function before the alert runs.deferBackgroundReturns: voidArg: _DeferBlock => ^()_.deferBackground(_DeferBlock func) added
_.deferBackground(^{ SS.alert(@"deferred"); });
// Returns from the function before the alert runs.
_.throttle(function, wait)docs
var throttled = _.throttle(updatePosition, 100); $(window).scroll(throttled);
Arg: _ThrottleBlock => ^id(id arg1, ...) nil terminated
_.throttle(_ThrottleBlock func, I waitNS, id arg1, ...)
nil terminated
_ThrottledBlock throttled = _.throttle(^id(id arg1, ...){ /* updatePosition */ return nil; }, 100, nil);
_.debounce(function, wait, [immediate])docs
var lazyLayout = _.debounce(calculateLayout, 300); $(window).resize(lazyLayout);
Arg: _DebounceBlock => ^(id arg1, ...) nil terminated
_.debounce(_DebounceBlock func, I waitNS, B immediate, id arg1, ...)
nil terminated
_DebouncedBlock lazyLayout = _.debounce(^(id arg1, ...){ /* calculateLayout */ }, 300, true, nil);
_.once(function)docs
var initialize = _.once(createApplication); initialize(); initialize(); // Application is only created once.
Arg: _OnceBlock => ^id(id arg1, ...) nil terminated
_.once(_OnceBlock func, id arg1, ...) nil terminated
_OncedBlock initialize = _.once(^id(id arg1, ...){ /* createApplication */ return nil; }, nil);
initialize(nil);
initialize(nil);
// Application is only created once.
_.after(count, function)docs
var renderNotes = _.after(notes.length, render);
_.each(notes, function(note) {
note.asyncSave({success: renderNotes});
});
// renderNotes is run once, after all notes have saved.Arg: _AfterBlock => ^id(id arg1, ...) nil terminated
_.after(I times, _AfterBlock func, id arg1, ...) nil terminated
_AfteredBlock renderNotes = _.after(notes.length, ^id(id arg1, ...){ /* render */ return nil; }, nil);
_.each(notes, ^(Note* note, ...) {
note.asyncSave(OKV({@"success", renderNotes}));
});
// renderNotes is run once, after all notes have saved.
_.wrap(function, wrapper)docs
var hello = function(name) { return "hello: " + name; };
hello = _.wrap(hello, function(func) {
return "before, " + func("moe") + ", after";
});
hello();
=> 'before, hello: moe, after'Arg: _WrapBlock => ^id(_WrappedBlock wrapped, id arg1, ...)
nil terminated
_.wrap(_WrappedBlock func, _WrapBlock wrapper)
_WrappedBlock hello = ^NSS*(NSS* name, ...) { return @"hello: ".add(name); };
hello = _.wrap(hello, ^NSS*(_WrappedBlock func, id arg1, ...) {
return @"before, ".add(func(@"moe", nil)).add(@", after");
});
hello(nil, nil);
=> 'before, hello: moe, after'
_.compose(*functions)docs
var greet = function(name){ return "hi: " + name; };
var exclaim = function(statement){ return statement + "!"; };
var welcome = _.compose(exclaim, greet);
welcome('moe');
=> 'hi: moe!'Arg: _ComposeBlock => ^id(id arg1, ...) nil terminated
_.compose(_ComposeBlock func1, ...) nil terminated
_ComposeBlock greet = ^(NSS* name, ...){ return @"hi: ".add(name); };
_ComposeBlock exclaim = ^(NSS* statement, ...){ return statement.add(@"!"); };
_ComposedBlock welcome = _.compose(exclaim, greet, nil);
welcome(@"moe");
=> 'hi: moe!'
_.keys(object)docs
_.keys({one : 1, two : 2, three : 3});
=> ["one", "two", "three"]_.keys(NSD* obj)
_.keys(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}));
=> ["one", "two", "three"]
_.values(object)docs
_.values({one : 1, two : 2, three : 3});
=> [1, 2, 3]_.values(NSD* obj)
_.values(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}));
=> [1, 2, 3]
_.functions(list, iterator, [context])docs
_.functions(_); => ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
_.functions(NSD* obj) no iterator
_.functions(OKV({@"map", _.map}, {@"reduce", _.reduce}));
=> ["map", "reduce"]Note: _.functions does not yet collect function names from classes. It only collects functions from an NSDictionary in block form.
_.extend(destination, *sources)docs
_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}_.extend(O* destination, NSD* source1, ...) nil terminated
_.extend(OKV({@"name", @"moe"}), OKV({@"age", N.I(50)}), nil);
=> {name : 'moe', age : 50}
_.pick(object, *keys)docs
_.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age');
=> {name : 'moe', age : 50}_.pick(O* obj, id key1, ...) nil terminated
_.pick(OKV({@"name", @"moe"}, {@"age", N.I(50)}, {@"userid", @"moe1"}), @"name", @"age", nil);
=> {name : 'moe', age : 50}
_.defaults(object, *defaults)docs
var iceCream = {flavor : "chocolate"};
_.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"});
=> {flavor : "chocolate", sprinkles : "lots"}_.defaults(O* obj, NSD* default1, ...) nil terminated
O* iceCream = OKV({@"flavor", @"chocolate"});
_.defaults(iceCream, OKV({@"flavor", @"vanilla"}, {@"sprinkles", @"lots"}), nil);
=> {flavor : "chocolate", sprinkles : "lots"}
_.clone(object)docs
_.clone({name : 'moe'});
=> {name : 'moe'};_.clone(NSO* obj)
_.clone(OKV({@"name", @"moe"}));
=> {name : 'moe'};
_.tap(object, interceptor)docs
_.chain([1,2,3,200])
.filter(function(num) { return num % 2 == 0; })
.tap(alert)
.map(function(num) { return num * num })
.value();
=> // [2, 200] (alerted)
=> [4, 40000]_.tap(NSO* obj, _TapBlock interceptor)
_.chain(AI(1,2,3,200))
.filter(^B(N* num, ...) { return num.I % 2 == 0; })
.tap(SS.alert)
.map(^(N* num, ...) { return N.I(num.I * num.I); })
.value();
=> // [2, 200] (alerted)
=> [4, 40000]OR:
_.chain(AI(1,2,3,200))
.filter(^B(N* num, ...) { return num.I % 2 == 0; })
.tap(SS.alert)
.map(^(N* num, ...) { return N.I(num.I * num.I); })
.NSA;
=> // [2, 200] (alerted)
=> [4, 40000]
_.has(object, key)docs
_.has({a: 1, b: 2, c: 3}, "b");
=> true_.has(NSO* obj, id key)
_.has(OKV({@"a", N.I(1)}, {@"b", N.I(2)}, {@"c", N.I(3)}), @"b");
=> true
_.isEqual(object, other)docs
var moe = {name : 'moe', luckyNumbers : [13, 27, 34]};
var clone = {name : 'moe', luckyNumbers : [13, 27, 34]};
moe == clone;
=> false
_.isEqual(moe, clone);
=> true_.isEqual(id a, id b)
O* moe = OKV({@"name", @"moe"}, {@"luckyNumbers", AI(13, 27, 34)});
O* clone = OKV({@"name", @"moe"}, {@"luckyNumbers", AI(13, 27, 34)});
moe == clone;
=> false
_.isEqual(moe, clone);
=> true
_.isEmpty(object)docs
_.isEmpty([1, 2, 3]);
=> false
_.isEmpty({});
=> true_.isEmpty(NSO* obj)
_.isEmpty(AI(1, 2, 3)); => false _.isEmpty(O.new); => true
_.isElement(object)docs
_.isElement(jQuery('body')[0]);
=> trueXJavaScript only
_.isArray(object)docs
(function(){ return _.isArray(arguments); })();
=> false
_.isArray([1,2,3]);
=> true_.isArray(id obj) difference: arguments are arrays
(^(id arg1, ...){ ARGS_AO(arguments, arg1); _.isArray(arguments); })(nil);
=> true // DIFFERENT - arguments are arrays!
_.isArray(AI(1,2,3));
=> true
_.isObject(value)docs
_.isObject({});
=> true
_.isObject(1);
=> false_.isObject(id obj)
_.isObject(O.new); => true _.isObject(N.I(1)); => false
_.isArguments(object)docs
(function(){ return _.isArguments(arguments); })(1, 2, 3);
=> true
_.isArguments([1,2,3]);
=> false_.isArguments(id obj)
(^(I arg1, ...){ ARGS_AI(arguments, arg1); _.isArguments(arguments); })(1, 2, 3, AI_END);
=> true
_.isArguments(AI(1,2,3));
=> false
_.isFunction(object)docs
_.isFunction(alert); => true
_.isFunction(id obj, id target) difference: added argument
_.isFunction(_.isFunction, nil); => true
_.isString(object)docs
_.isString("moe");
=> true_.isString(id obj)
_.isString(@"moe"); => true
_.isNumber(object)docs
_.isNumber(8.4 * 5); => true
_.isNumber(id obj)
_.isNumber(N.F(8.4 * 5)); => true
_.isFinite(object)docs
_.isFinite(-101); => true _.isFinite(-Infinity); => false
_.isFinite(id obj)
_.isFinite(N.I(-101)); => true _.isFinite(NF_NEG_INFINITY); => false
_.isBoolean(object)docs
_.isBoolean(null); => false
_.isBoolean(id obj)
_.isBoolean(nil); => false
_.isDate(object)docs
_.isDate(new Date()); => true
_.isDate(id obj)
_.isDate(Date.new); => true
_.isRegExp(object)docs
_.isRegExp(/moe/); => true
XJavaScript only
_.isNaN(object)docs
_.isNaN(NaN); => true isNaN(undefined); => true _.isNaN(undefined); => false
_.isNaN(id obj)
_.isNaN(NF_NaN); => true
_.isNull(object)docs
_.isNull(null); => true _.isNull(undefined); => false
_.isNull(id obj)
_.isNull(nil); => true _.isNull(NSNull.null); => true
_.isUndefined(variable)docs
_.isUndefined(window.missingVariable); => true
XJavaScript only
_.noConflict()docs
var underscore = _.noConflict();
XJavaScript only
_.identity(value)docs
var moe = {name : 'moe'};
moe === _.identity(moe);
=> true_.identity(value)
O* moe = OKV({@"name", @"moe"});
moe === _.identity(moe);
=> trueidentityTruthyReturns: _ItemTestBlock => ^B(id value, ...) specialized ... => id key, id list_.identityTruthy(value)
O* moe = OKV({@"name", @"moe"});
_.identityTruthy(moe);
=> true
_.times(n, iterator)docs
_(3).times(function(){ genie.grantWish(); });_.times(UI n, _TimesBlock iterator)
__(N.I(3)).times(^(UI index){ genie.grantWish(); });
_.mixin(object)docs
_.mixin({
capitalize : function(string) {
return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
}
});
_("fabio").capitalize();
=> "Fabio"XJavaScript only
_.uniqueId([prefix])docs
_.uniqueId('contact_');
=> 'contact_104'_.uniqueId(NSS* prefix) prefix or nil
_.uniqueId(@"contact_"); => 'contact_104'
_.escape(string)docs
_.escape('Curly, Larry & Moe');
=> "Curly, Larry & Moe"XJavaScript only
_.result(object, property)docs
var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
_.result(object, 'cheese');
=> "crumpets"
_.result(object, 'stuff');
=> "nonsense"_.result(NSO* object, id property)
O* object = OKV({@"cheese", @"crumpets"}, {@"stuff", ^(){ return @"nonsense"; }});
_.result(object, @"cheese");
=> "crumpets"
_.result(object, @"stuff");
=> "nonsense"
_.template(templateString, [data], [settings])docs
var compiled = _.template("hello: <%= name %>");
compiled({name : 'moe'});
=> "hello: moe"XJavaScript only
_(value)_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });
__(id value) change_.map(AI(1, 2, 3), ^(N* n, ...){ return N.I(n.I * 2); });
__(AI(1, 2, 3)).map(^(N* n, ...){ return N.I(n.I * 2); });
In addition, the prototype's methods are proxied through the chained Underscore object.
pushreverseshiftsort
spliceunshiftconcatjoinslice
poppushreverseshiftsort
spliceunshiftconcatjoinslice
_.chain(obj)docs
var stooges = [{name : 'curly', age : 25}, {name : 'moe', age : 21}, {name : 'larry', age : 23}];
var youngest = _.chain(stooges)
.sortBy(function(stooge){ return stooge.age; })
.map(function(stooge){ return stooge.name + ' is ' + stooge.age; })
.first()
.value();
=> "moe is 21"_.chain(id obj)
A* stooges = AO(OKV({@"name", @"curly"}, {@"age", N.I(25)}), OKV({@"name", @"moe"}, {@"age", N.I(21)}), OKV({@"name", @"larry"}, {@"age", N.I(23)}));
_.chain(stooges)
.sortBy(^(O* stooge, ...){ return stooge.get(@"age"); })
.map(^(O* stooge, ...){ return stooge.get(@"name").add(@" is ").add((NSS*)stooge.get(@"age")); })
.first(-1)
.value();
=> "moe is 21"OR
A* stooges = AO(OKV({@"name", @"curly"}, {@"age", N.I(25)}), OKV({@"name", @"moe"}, {@"age", N.I(21)}), OKV({@"name", @"larry"}, {@"age", N.I(23)}));
youngest = _.chain(stooges)
.sortBy(^(O* stooge, ...){ return stooge.get(@"age"); })
.map(^(O* stooge, ...){ return stooge.get(@"name").add(@" is ").add((NSS*)stooge.get(@"age")); })
.first(-1)
.O;
=> "moe is 21"
_(obj).value()docs
_([1, 2, 3]).value(); => [1, 2, 3]
__(obj).value() change
__([1, 2, 3]).value(); => [1, 2, 3]
__(obj).I __(obj).UI __(obj).B __(obj).F __(obj).D specialized
__(N.I(-1)).I; => -1 __(N.I(1)).UI; => 1 __(N.B(true)).B; => true __(N.F(1.2f)).F; => 1.2 __(N.D(3.14159)).D; => 3.14159
__(obj).NSS __(obj).S toMutable
__(obj).NSA __(obj).A toMutable
__(obj).NSD __(obj).O toMutable
__(obj).Date specialized
__(@"bob").NSS;
=> @"bob"
__(@"bob").S;
=> @"bob"
__(AI(1, 2, 3)).NSA;
=> [1, 2, 3]
__(AI(1, 2, 3)).A;
=> [1, 2, 3]
__(OKV({@"key", N.I(1)})).NSD;
=> {key: 1}
__(OKV({@"key", N.I(1)})).O;
=> {key: 1}
__(Date.new).Date;
=> now
X_.m only
isTruthy checks for things that are not nil, not empty, etc.
Returns: B_.isTruthy(id obj)
_.isTruthy(@"hello"); => true _.isTruthy(@""); => false
X_.m only
isTruthy checks for things that are nil, empty, etc.
Returns: B_.isFalsy(id obj)
_.isFalsy(@""); => true _.isFalsy(@"hello"); => false
X_.m only
isTruthy checks whether something is an NSDictionary or NSMutableDictionary.
Returns: B_.isDictionary(id obj)
_.isDictionary(NSD.new); => true _.isDictionary(O.new); => true _.isDictionary(N.B(true)); => false
X_.m only
isTruthy checks whether something is a block.
Returns: B_.isBlock(id obj)
_.isBlock(^{});
=> true
_.isBlock(N.B(true));
=> false
X_.m only
Generates an _ItemTestBlock that can be used to test arrays by a non-string property value (for example: _.filter, _.reject, _.all, _.any).
Returns: _ItemTestBlock => ^B(id value, ...)_.valueTester(NSS* key, id match)
A* objects = AO(OKV({@"name", @"bob"}, {@"owner", owner1}), OKV({@"name", @"george"}, {@"owner", owner2}), OKV({@"name", @"fred"}, {@"owner", owner1}));
_.pluck(_.filter(objects, _.valueTester(@"owner", owner1)), @"name");
=> ["bob", "fred"]
X_.m only
Generates an _ItemTestBlock that can be used to test arrays by a string property value (for example: _.filter, _.reject, _.all, _.any).
Returns: _ItemTestBlock => ^B(id value, ...)_.valueStringTester(NSS* key, NSS* match)
A* objects = AO(OKV({@"name", @"bob"}, {@"owner", @"this guy"}), OKV({@"name", @"george"}, {@"owner", @"that guy"}), OKV({@"name", @"fred"}, {@"owner", @"this guy"}));
_.pluck(_.filter(objects, _.valueStringTester(@"owner", @"this guy")), @"name");
=> ["bob", "fred"]
X_.m only
Sets all the given properties to a given value.
Returns: void_.setProps(NSA* array, NSS* key, id value)
A* objects = AO(OKV({@"name", @"bob"}, {@"owner", owner1}), OKV({@"name", @"george"}, {@"owner", owner2}), OKV({@"name", @"fred"}, {@"owner", owner1}));
_.setProps(objects, @"owner", nil);
SS.stringify(_.pluck(objects, @"owner"));
=> [null,null,null]
X_.m only
Returns an array with all the class names in array. Useful for debugging.
Returns: A*_.classNames(NSA* array)
A* objects = AO(ValueOwner.new, ValueOwner.new); _.classNames(objects).join(@",") => ValueOwner,ValueOwner
ARGS_KEY(value): id key = {2nd arg};
ARGS_KEY_LIST(value, LIST_NAME): id key = {2nd arg}; id LIST_NAME = {3rd arg};
ARGS_INDEX(value): I index = {2nd arg};
ARGS_INDEX_LIST(value, LIST_NAME): I index = {2nd arg}; id LIST_NAME = {3rd arg};
ARGS_LIST(value, LIST_NAME): id LIST_NAME = {3rd arg};
ARGS_LIST(value, LIST_NAME): id LIST_NAME = {3rd arg};
ARGS_AO(NAME, lastNamedArg): A* NAME = {all ids after lastNamedArgument (inclusive)}; // nil
ARGS_AI(NAME, lastNamedArg): A* NAME = {all Is after lastNamedArgument (inclusive)}; // AI_END
ARG_B(NAME, lastNamedArg): B NAME = {one bool after lastNamedArgument};
ARG_I(NAME, lastNamedArg): I NAME = {one integer after lastNamedArgument};
ARG_UI(NAME, lastNamedArg): UI NAME = {one unsigned integer after lastNamedArgument};
ARG_F(NAME, lastNamedArg): F NAME = {one float after lastNamedArgument};
ARG_D(NAME, lastNamedArg): D NAME = {one double after lastNamedArgument};
ARG_N(NAME, lastNamedArg): N* NAME = {one NSNumber after lastNamedArgument};
ARG_NSO(NAME, lastNamedArg): NSO* NAME = {one NSObject or id after lastNamedArgument};
Variable Arguments: JavaScript allows arguments to be skipped, but Objective-C requires explicit declarations. To allow for the flexibility of Underscore.js, I used the C-style variable argument list: 'someFunction(id value ...)'.
For example, _.map optionally provides the key and collection.
A* doubled = _.map(AI(1, 2, 3), ^(N* num, ...){ return N.I(num.I * 2); });__block A* keys = A.new;
A* values = _.map(AO({@"int", N.I(1)}, {@"string", @"hello"}), ^(id value, ...){
ARGS_KEY(value); // helper macro to save the key in 'id key' variable;
keys.push(key);
return value;
});
// keys = [@"int", string]
// values = [1,@"hello"]
Or if you want to get extract variable arguments, you need to provide a nil terminator:
A* result = (A*) (^(id arg1, ...){ ARGS_AO(arguments, arg1); return _.flatten(arguments, false); })(N.I(1), AI(2), AO(N.I(3), AO(AO(AI(4)))), /* NIL_TERMINATION */ nil);
// result = [1,2,3,4]
_.m is still young but it already has much of the Underscore.js goodness! Please feel free to submit bug fixes and feature requests on GitHub