
var Zn = {};

Function.prototype.not =
  function () {
    var self = this;
    return function () {
      return ! self.apply (null, arguments);
    };
  };


Function.prototype.compose =
  function (f) {
    var self = this;
    return function () {
      return self (f.apply (null, arguments));
    };
  };


// Memoization
// 
Function.prototype.memo =
  function (f) {
    var self = this;
    var val = null;
    return function () {
      if (val == null) val = self.apply (null, arguments);
      return val;
    };
  };


Function.prototype.thunk =
  function() {
    var self = this;
    var args = arguments;
    return function() {
      return self.apply (null, args);
    };
  };


// min/max -------------------------------------------------------------

Zn.min = function (a, b, lessThan) {
  return lessThan == undefined ? (a < b ? a : b) : lessThan (a, b) ? a : b;
}


Zn.max = function (a, b, lessThan) {
  return lessThan == undefined ? (a < b ? b : a) : lessThan (a, b) ? b : a;
}


// Arrays -------------------------------------------------------------

Array.prototype.isEmpty = 
	function () {
    return this.length == 0;
  }


Array.prototype.nullIfEmpty = 
	function () {
    return this.isEmpty() ? null : this;
  }


Array.prototype.doAll = 
	function (forEach) {
    for (var i = 0; i < this.length; ++i) forEach (this[i]);
	};


Array.prototype.doUntil = 
	function (forEach, predicate) {
    for (var i = 0; i < this.length; ++i) {
      var element = this[i];
      if (predicate (element)) return;
      forEach (element);
    };
    return null;
	};


Array.prototype.detect = 
	function (predicate) {
    return this.doUntil (function() {}, predicate);
	};


Array.prototype.anySatisfy = 
	function (predicate) {
    return this.detect (predicate) != null;
	};

Array.prototype.allSatisfy = 
	function (predicate) {
    return this.detect (predicate.not()) = null;
	};


Array.prototype.includes = 
	function (obj) {
    return this.detect (
        function (element) { return element == obj }
      ) != null;
	};


Array.prototype.select = 
	function (predicate) {
    var result = [];
    this.doAll (
        function (each) { if (predicate (each)) result.push (each); }
      );
    return result;
  };


Array.prototype.reject = 
	function (predicate) {
    return this.select (predicate.not());
  };


Array.prototype.collect = 
	function (map) {
    var result = [];
    this.doAll (function (each) { result.push (map (each)) });
    return result;
  };


Array.prototype.inject = 
	function (value, into) {
    var value = value;
    this.doAll (function (each) { value = into (value, each) });
    return value;
  };


Array.prototype.project = 
	function (predicate, map) {
    var result = [];
    this.doAll (
        function (each) {
            if (predicate (each)) result.push (map (each));
          }
      );
    return result.nullIfEmpty();
  };


Array.prototype.min = 
  function() {
    if (this.isEmpty()) return null;
    return this.inject (
                    this[0],
                    function (val, each) { return Zn.min (val, each) }
                  );
  };

Array.prototype.max = 
  function() {
    if (this.isEmpty()) return null;
    return this.inject (
                    this[0],
                    function (val, each) { return Zn.max (val, each) }
                  );
  };

