diff --git a/index.js b/index.js index 8f3c0a88..fd44600c 100644 --- a/index.js +++ b/index.js @@ -169,6 +169,16 @@ } ); + // Ord :: TypeClass + var Ord = $.TypeClass( + 'sanctuary/Ord', + function(x) { + var type = _type(x); + return type === 'Number' && $.ValidNumber.test(x) || + type === 'String'; + } + ); + // Semigroup :: TypeClass var Semigroup = $.TypeClass( 'sanctuary/Semigroup', @@ -2347,6 +2357,52 @@ [$.FiniteNumber, $.NonZeroFiniteNumber, $.FiniteNumber], function(a, b) { return a / b; }); + //# min :: Ord a => a -> a -> a + //. + //. Returns the smaller of its two arguments. + //. + //. Strings are compared lexicographically. Specifically, the Unicode + //. code point value of each character in the first string is compared + //. to the value of the corresponding character in the second string. + //. + //. See also [`max`](#max). + //. + //. ```javascript + //. > S.min(10, 2) + //. 2 + //. + //. > S.min('10', '2') + //. '10' + //. ``` + S.min = + def('min', + {a: [Ord]}, + [a, a, a], + function(x, y) { return x < y ? x : y; }); + + //# max :: Ord a => a -> a -> a + //. + //. Returns the larger of its two arguments. + //. + //. Strings are compared lexicographically. Specifically, the Unicode + //. code point value of each character in the first string is compared + //. to the value of the corresponding character in the second string. + //. + //. See also [`min`](#min). + //. + //. ```javascript + //. > S.max(10, 2) + //. 10 + //. + //. > S.max('10', '2') + //. '2' + //. ``` + S.max = + def('max', + {a: [Ord]}, + [a, a, a], + function(x, y) { return x > y ? x : y; }); + //. ### Integer //# even :: Integer -> Boolean diff --git a/test/index.js b/test/index.js index 53c510a3..de08d411 100644 --- a/test/index.js +++ b/test/index.js @@ -3575,6 +3575,94 @@ describe('number', function() { }); + describe('min', function() { + + it('is a binary function', function() { + eq(typeof S.min, 'function'); + eq(S.min.length, 2); + }); + + it('type checks its arguments', function() { + assert.throws(function() { S.min(/x/); }, + errorEq(TypeError, + '‘min’ requires ‘a’ to implement Ord; ' + + 'RegExp does not')); + + assert.throws(function() { S.min(NaN); }, + errorEq(TypeError, + '‘min’ requires ‘a’ to implement Ord; ' + + 'Number does not')); + }); + + it('may be applied to (valid) numbers', function() { + eq(S.min(10, 2), 2); + eq(S.min(2, 10), 2); + eq(S.min(0.1, 0.01), 0.01); + eq(S.min(0.01, 0.1), 0.01); + eq(S.min(Infinity, -Infinity), -Infinity); + eq(S.min(-Infinity, Infinity), -Infinity); + }); + + it('may be applied to strings', function() { + eq(S.min('abc', 'xyz'), 'abc'); + eq(S.min('xyz', 'abc'), 'abc'); + eq(S.min('10', '2'), '10'); + eq(S.min('2', '10'), '10'); + eq(S.min('A', 'a'), 'A'); + eq(S.min('a', 'A'), 'A'); + }); + + it('is curried', function() { + eq(S.min(10).length, 1); + eq(S.min(10)(2), 2); + }); + + }); + + describe('max', function() { + + it('is a binary function', function() { + eq(typeof S.max, 'function'); + eq(S.max.length, 2); + }); + + it('type checks its arguments', function() { + assert.throws(function() { S.max(/x/); }, + errorEq(TypeError, + '‘max’ requires ‘a’ to implement Ord; ' + + 'RegExp does not')); + + assert.throws(function() { S.max(NaN); }, + errorEq(TypeError, + '‘max’ requires ‘a’ to implement Ord; ' + + 'Number does not')); + }); + + it('may be applied to (valid) numbers', function() { + eq(S.max(10, 2), 10); + eq(S.max(2, 10), 10); + eq(S.max(0.1, 0.01), 0.1); + eq(S.max(0.01, 0.1), 0.1); + eq(S.max(Infinity, -Infinity), Infinity); + eq(S.max(-Infinity, Infinity), Infinity); + }); + + it('may be applied to strings', function() { + eq(S.max('abc', 'xyz'), 'xyz'); + eq(S.max('xyz', 'abc'), 'xyz'); + eq(S.max('10', '2'), '2'); + eq(S.max('2', '10'), '2'); + eq(S.max('A', 'a'), 'a'); + eq(S.max('a', 'A'), 'a'); + }); + + it('is curried', function() { + eq(S.max(10).length, 1); + eq(S.max(10)(2), 10); + }); + + }); + }); describe('integer', function() {