From 195889514c00e34c5b0222ee60695625394c840b Mon Sep 17 00:00:00 2001 From: David Chambers Date: Fri, 11 Nov 2016 23:31:41 +0100 Subject: [PATCH] logic: make S.and and S.or monomorphic, and remove S.xor The first of the three functions to be added was S.or, to allow: > S.or(S.Nothing, S.Just(42)) Just(42) This is possible in Haskell too: > Nothing <|> Just 42 Just 42 The <|> operator, though, does not accept Boolean operands, as the Bool type does not satisfy the requirements of the Alternative type class. The Bool type has two monoids: - All - mempty = True - mappend = (&&) - Any - mempty = False - mappend = (||) Overloading S.or for Boolean arguments requires implicitly selecting All as the Boolean monoid. This necessitates the definition and use of a Sanctuary-specific Monoid-like type class (the standard Monoid type class defined in sanctuary-type-classes rejects Boolean values because fantasy-land/empty is not defined for the Boolean type). This commit updates the type of S.or to match that of Haskell's (||): S.or :: Boolean -> Boolean -> Boolean The <|> behaviour currently provided by S.or is undoubtedly useful, but will be provided by S.alt once Sanctuary wrappers are added for the functions defined in sanctuary-type-classes: S.alt :: Alt f => f a -> f a -> f a S.and is currently polymorphic for consistency with S.or rather than to address any particular need. This commit updates the type of S.and to match that of Haskell's (&&): S.and :: Boolean -> Boolean -> Boolean Boolean XOR does not warrant inclusion, so this commit removes S.xor. This commit also removes the "toBoolean" methods which existed solely to allow S.and, S.or, and S.xor to operate on Maybe and Either values. --- index.js | 159 +++++++++--------------------------------- test/Either/Left.js | 5 -- test/Either/Right.js | 5 -- test/Maybe/Just.js | 5 -- test/Maybe/Nothing.js | 5 -- test/and.js | 76 ++------------------ test/or.js | 76 ++------------------ test/xor.js | 135 ----------------------------------- 8 files changed, 42 insertions(+), 424 deletions(-) delete mode 100644 test/xor.js diff --git a/index.js b/index.js index f9de4df9..112358a4 100644 --- a/index.js +++ b/index.js @@ -284,15 +284,6 @@ } ); - // Monoid :: TypeClass - var Monoid = $.TypeClass( - 'sanctuary/Monoid', - function(x) { - return R.contains(R.type(x), ['Array', 'Boolean', 'String']) || - hasMethod('empty')(x); - } - ); - // Ord :: TypeClass var Ord = $.TypeClass( 'sanctuary/Ord', @@ -1096,26 +1087,6 @@ [$Maybe(a), $.Function, b], Maybe$prototype$sequence); - //# Maybe#toBoolean :: Maybe a ~> () -> Boolean - //. - //. Returns `false` if `this` is Nothing; `true` if `this` is a Just. - //. - //. ```javascript - //. > S.Nothing.toBoolean() - //. false - //. - //. > S.Just(42).toBoolean() - //. true - //. ``` - function Maybe$prototype$toBoolean(self) { - return self.isJust; - } - Maybe.prototype.toBoolean = - method('Maybe#toBoolean', - {}, - [$Maybe(a), $.Boolean], - Maybe$prototype$toBoolean); - //# Maybe#toString :: Maybe a ~> () -> String //. //. Returns the string representation of the Maybe. @@ -1704,26 +1675,6 @@ [$Either(a, b), $.Function, c], Either$prototype$sequence); - //# Either#toBoolean :: Either a b ~> () -> Boolean - //. - //. Returns `false` if `this` is a Left; `true` if `this` is a Right. - //. - //. ```javascript - //. > S.Left(42).toBoolean() - //. false - //. - //. > S.Right(42).toBoolean() - //. true - //. ``` - function Either$prototype$toBoolean(self) { - return self.isRight; - } - Either.prototype.toBoolean = - method('Either#toBoolean', - {}, - [$Either(a, b), $.Boolean], - Either$prototype$toBoolean); - //# Either#toString :: Either a b ~> () -> String //. //. Returns the string representation of the Either. @@ -2033,108 +1984,62 @@ S.eitherToMaybe = def('eitherToMaybe', {}, [$Either(a, b), $Maybe(b)], eitherToMaybe); - //. ### Alternative - - // Alternative :: TypeClass - var Alternative = $.TypeClass( - 'Alternative', - function(x) { - return R.contains(R.type(x), ['Array', 'Boolean']) || - hasMethod('toBoolean')(x); - } - ); - - // toBoolean :: Alternative a => a -> Boolean - var toBoolean = function(x) { - switch (R.type(x)) { - case 'Array': return x.length > 0; - case 'Boolean': return x.valueOf(); - default: return x.toBoolean(); - } - }; - - // empty :: Monoid a => a -> a - var empty = function(x) { - switch (R.type(x)) { - case 'Array': return []; - case 'Boolean': return false; - default: return x.empty(); - } - }; + //. ### Logic - //# and :: Alternative a => a -> a -> a + //# and :: Boolean -> Boolean -> Boolean //. - //. Takes two values of the same type and returns the second value - //. if the first is "true"; the first value otherwise. An array is - //. considered "true" if its length is greater than zero. The Boolean - //. value `true` is also considered "true". Other types must provide - //. a `toBoolean` method. + //. Boolean "and". //. //. ```javascript - //. > S.and(S.Just(1), S.Just(2)) - //. Just(2) + //. > S.and(false, false) + //. false //. - //. > S.and(S.Nothing, S.Just(3)) - //. Nothing + //. > S.and(false, true) + //. false + //. + //. > S.and(true, false) + //. false + //. + //. > S.and(true, true) + //. true //. ``` function and(x, y) { - return toBoolean(x) ? y : x; + return x.valueOf() && y.valueOf(); } - S.and = def('and', {a: [Alternative]}, [a, a, a], and); + S.and = def('and', {}, [$.Boolean, $.Boolean, $.Boolean], and); - //# or :: Alternative a => a -> a -> a + //# or :: Boolean -> Boolean -> Boolean //. - //. Takes two values of the same type and returns the first value if it - //. is "true"; the second value otherwise. An array is considered "true" - //. if its length is greater than zero. The Boolean value `true` is also - //. considered "true". Other types must provide a `toBoolean` method. + //. Boolean "or". //. //. ```javascript - //. > S.or(S.Just(1), S.Just(2)) - //. Just(1) - //. - //. > S.or(S.Nothing, S.Just(3)) - //. Just(3) - //. ``` - function or(x, y) { - return toBoolean(x) ? x : y; - } - S.or = def('or', {a: [Alternative]}, [a, a, a], or); - - //# xor :: (Alternative a, Monoid a) => a -> a -> a + //. > S.or(false, false) + //. false //. - //. Takes two values of the same type and returns the "true" value - //. if one value is "true" and the other is "false"; otherwise it - //. returns the type's "false" value. An array is considered "true" - //. if its length is greater than zero. The Boolean value `true` is - //. also considered "true". Other types must provide `toBoolean` and - //. `empty` methods. + //. > S.or(false, true) + //. true //. - //. ```javascript - //. > S.xor(S.Nothing, S.Just(1)) - //. Just(1) + //. > S.or(true, false) + //. true //. - //. > S.xor(S.Just(2), S.Just(3)) - //. Nothing + //. > S.or(true, true) + //. true //. ``` - function xor(x, y) { - return toBoolean(x) === toBoolean(y) ? empty(x) : or(x, y); + function or(x, y) { + return x.valueOf() || y.valueOf(); } - S.xor = def('xor', {a: [Alternative, Monoid]}, [a, a, a], xor); - - //. ### Logic + S.or = def('or', {}, [$.Boolean, $.Boolean, $.Boolean], or); //# not :: Boolean -> Boolean //. - //. Takes a Boolean and returns the negation of that value - //. (`false` for `true`; `true` for `false`). + //. Boolean "not". //. //. ```javascript - //. > S.not(true) - //. false - //. //. > S.not(false) //. true + //. + //. > S.not(true) + //. false //. ``` function not(x) { return !x.valueOf(); diff --git a/test/Either/Left.js b/test/Either/Left.js index 4f2b77bf..15ab1cdd 100644 --- a/test/Either/Left.js +++ b/test/Either/Left.js @@ -187,11 +187,6 @@ describe('Left', function() { eq(S.Left('abc').sequence(S.Maybe.of), S.Just(S.Left('abc'))); }); - it('provides a "toBoolean" method', function() { - eq(S.Left('abc').toBoolean.length, 0); - eq(S.Left('abc').toBoolean(), false); - }); - it('provides a "toString" method', function() { eq(S.Left('abc').toString.length, 0); eq(S.Left('abc').toString(), 'Left("abc")'); diff --git a/test/Either/Right.js b/test/Either/Right.js index 2b397799..59f0140f 100644 --- a/test/Either/Right.js +++ b/test/Either/Right.js @@ -187,11 +187,6 @@ describe('Right', function() { eq(S.Right(S.Just(42)).sequence(S.Maybe.of), S.Just(S.Right(42))); }); - it('provides a "toBoolean" method', function() { - eq(S.Right(42).toBoolean.length, 0); - eq(S.Right(42).toBoolean(), true); - }); - it('provides a "toString" method', function() { eq(S.Right([1, 2, 3]).toString.length, 0); eq(S.Right([1, 2, 3]).toString(), 'Right([1, 2, 3])'); diff --git a/test/Maybe/Just.js b/test/Maybe/Just.js index 6ef20890..f1006d5a 100644 --- a/test/Maybe/Just.js +++ b/test/Maybe/Just.js @@ -215,11 +215,6 @@ describe('Just', function() { eq(S.Just(S.Right(42)).sequence(S.Either.of), S.Right(S.Just(42))); }); - it('provides a "toBoolean" method', function() { - eq(S.Just(42).toBoolean.length, 0); - eq(S.Just(42).toBoolean(), true); - }); - it('provides a "toString" method', function() { eq(S.Just([1, 2, 3]).toString.length, 0); eq(S.Just([1, 2, 3]).toString(), 'Just([1, 2, 3])'); diff --git a/test/Maybe/Nothing.js b/test/Maybe/Nothing.js index f71cea16..550fcec7 100644 --- a/test/Maybe/Nothing.js +++ b/test/Maybe/Nothing.js @@ -178,11 +178,6 @@ describe('Nothing', function() { eq(S.Nothing.sequence(S.Either.of), S.Right(S.Nothing)); }); - it('provides a "toBoolean" method', function() { - eq(S.Nothing.toBoolean.length, 0); - eq(S.Nothing.toBoolean(), false); - }); - it('provides a "toString" method', function() { eq(S.Nothing.toString.length, 0); eq(S.Nothing.toString(), 'Nothing'); diff --git a/test/and.js b/test/and.js index 87928926..b377988f 100644 --- a/test/and.js +++ b/test/and.js @@ -1,13 +1,8 @@ 'use strict'; -var throws = require('assert').throws; - -var R = require('ramda'); - var S = require('..'); var eq = require('./internal/eq'); -var errorEq = require('./internal/errorEq'); describe('and', function() { @@ -17,76 +12,15 @@ describe('and', function() { eq(S.and.length, 2); }); - it('can be applied to Booleans', function() { + it('has && semantics', function() { eq(S.and(false, false), false); eq(S.and(false, true), false); eq(S.and(true, false), false); eq(S.and(true, true), true); - }); - - it('can be applied to arrays', function() { - eq(S.and([], []), []); - eq(S.and([], [42]), []); - eq(S.and([42], []), []); - eq(S.and([42], [43]), [43]); - }); - - it('can be applied to maybes', function() { - eq(S.and(S.Nothing, S.Nothing), S.Nothing); - eq(S.and(S.Nothing, S.Just(42)), S.Nothing); - eq(S.and(S.Just(42), S.Nothing), S.Nothing); - eq(S.and(S.Just(42), S.Just(43)), S.Just(43)); - }); - - it('can be applied to eithers', function() { - eq(S.and(S.Left('foo'), S.Left('bar')), S.Left('foo')); - eq(S.and(S.Left('foo'), S.Right(42)), S.Left('foo')); - eq(S.and(S.Right(42), S.Left('foo')), S.Left('foo')); - eq(S.and(S.Right(42), S.Right(43)), S.Right(43)); - }); - - it('throws if applied to values of different types', function() { - throws(function() { S.and([], false); }, - errorEq(TypeError, - 'Type-variable constraint violation\n' + - '\n' + - 'and :: Alternative a => a -> a -> a\n' + - ' ^ ^\n' + - ' 1 2\n' + - '\n' + - '1) [] :: Array ???\n' + - '\n' + - '2) false :: Boolean\n' + - '\n' + - 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); - - throws(function() { S.and(R.__, false)([]); }, - errorEq(TypeError, - 'Type-variable constraint violation\n' + - '\n' + - 'and :: Alternative a => a -> a -> a\n' + - ' ^ ^\n' + - ' 1 2\n' + - '\n' + - '1) [] :: Array ???\n' + - '\n' + - '2) false :: Boolean\n' + - '\n' + - 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); - }); - - it('throws if applied to values without a "toBoolean" method', function() { - throws(function() { S.and(0, 1); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'and :: Alternative a => a -> a -> a\n' + - ' ^^^^^^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) 0 :: Number, FiniteNumber, Integer, ValidNumber\n' + - '\n' + - '‘and’ requires ‘a’ to satisfy the Alternative type-class constraint; the value at position 1 does not.\n')); + eq(S.and(new Boolean(false), new Boolean(false)), false); + eq(S.and(new Boolean(false), new Boolean(true)), false); + eq(S.and(new Boolean(true), new Boolean(false)), false); + eq(S.and(new Boolean(true), new Boolean(true)), true); }); }); diff --git a/test/or.js b/test/or.js index 111e827b..7177cad4 100644 --- a/test/or.js +++ b/test/or.js @@ -1,13 +1,8 @@ 'use strict'; -var throws = require('assert').throws; - -var R = require('ramda'); - var S = require('..'); var eq = require('./internal/eq'); -var errorEq = require('./internal/errorEq'); describe('or', function() { @@ -17,76 +12,15 @@ describe('or', function() { eq(S.or.length, 2); }); - it('can be applied to Booleans', function() { + it('has || semantics', function() { eq(S.or(false, false), false); eq(S.or(false, true), true); eq(S.or(true, false), true); eq(S.or(true, true), true); - }); - - it('can be applied to arrays', function() { - eq(S.or([], []), []); - eq(S.or([], [42]), [42]); - eq(S.or([42], []), [42]); - eq(S.or([42], [43]), [42]); - }); - - it('can be applied to maybes', function() { - eq(S.or(S.Nothing, S.Nothing), S.Nothing); - eq(S.or(S.Nothing, S.Just(42)), S.Just(42)); - eq(S.or(S.Just(42), S.Nothing), S.Just(42)); - eq(S.or(S.Just(42), S.Just(43)), S.Just(42)); - }); - - it('can be applied to eithers', function() { - eq(S.or(S.Left('foo'), S.Left('bar')), S.Left('bar')); - eq(S.or(S.Left('foo'), S.Right(42)), S.Right(42)); - eq(S.or(S.Right(42), S.Left('foo')), S.Right(42)); - eq(S.or(S.Right(42), S.Right(43)), S.Right(42)); - }); - - it('throws if applied to values of different types', function() { - throws(function() { S.or([], false); }, - errorEq(TypeError, - 'Type-variable constraint violation\n' + - '\n' + - 'or :: Alternative a => a -> a -> a\n' + - ' ^ ^\n' + - ' 1 2\n' + - '\n' + - '1) [] :: Array ???\n' + - '\n' + - '2) false :: Boolean\n' + - '\n' + - 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); - - throws(function() { S.or(R.__, false)([]); }, - errorEq(TypeError, - 'Type-variable constraint violation\n' + - '\n' + - 'or :: Alternative a => a -> a -> a\n' + - ' ^ ^\n' + - ' 1 2\n' + - '\n' + - '1) [] :: Array ???\n' + - '\n' + - '2) false :: Boolean\n' + - '\n' + - 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); - }); - - it('throws if applied to values without a "toBoolean" method', function() { - throws(function() { S.or(0, 1); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'or :: Alternative a => a -> a -> a\n' + - ' ^^^^^^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) 0 :: Number, FiniteNumber, Integer, ValidNumber\n' + - '\n' + - '‘or’ requires ‘a’ to satisfy the Alternative type-class constraint; the value at position 1 does not.\n')); + eq(S.or(new Boolean(false), new Boolean(false)), false); + eq(S.or(new Boolean(false), new Boolean(true)), true); + eq(S.or(new Boolean(true), new Boolean(false)), true); + eq(S.or(new Boolean(true), new Boolean(true)), true); }); }); diff --git a/test/xor.js b/test/xor.js deleted file mode 100644 index b79b9d22..00000000 --- a/test/xor.js +++ /dev/null @@ -1,135 +0,0 @@ -'use strict'; - -var throws = require('assert').throws; - -var R = require('ramda'); - -var S = require('..'); - -var eq = require('./internal/eq'); -var errorEq = require('./internal/errorEq'); - - -describe('xor', function() { - - it('is a binary function', function() { - eq(typeof S.xor, 'function'); - eq(S.xor.length, 2); - }); - - it('can be applied to Booleans', function() { - eq(S.xor(false, false), false); - eq(S.xor(false, true), true); - eq(S.xor(true, false), true); - eq(S.xor(true, true), false); - }); - - it('can be applied to arrays', function() { - eq(S.xor([], []), []); - eq(S.xor([], [42]), [42]); - eq(S.xor([42], []), [42]); - eq(S.xor([42], [43]), []); - }); - - it('can be applied to maybes', function() { - eq(S.xor(S.Nothing, S.Nothing), S.Nothing); - eq(S.xor(S.Nothing, S.Just(42)), S.Just(42)); - eq(S.xor(S.Just(42), S.Nothing), S.Just(42)); - eq(S.xor(S.Just(42), S.Just(43)), S.Nothing); - }); - - it('cannot be applied to eithers', function() { - throws(function() { S.xor(S.Left('foo'), S.Left('bar')); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) Left("foo") :: Either String ???\n' + - '\n' + - '‘xor’ requires ‘a’ to satisfy the Monoid type-class constraint; the value at position 1 does not.\n')); - - throws(function() { S.xor(S.Left('foo'), S.Right(42)); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) Left("foo") :: Either String ???\n' + - '\n' + - '‘xor’ requires ‘a’ to satisfy the Monoid type-class constraint; the value at position 1 does not.\n')); - - throws(function() { S.xor(S.Right(42), S.Left('foo')); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) Right(42) :: Either ??? Number, Either ??? FiniteNumber, Either ??? NonZeroFiniteNumber, Either ??? Integer, Either ??? ValidNumber\n' + - '\n' + - '‘xor’ requires ‘a’ to satisfy the Monoid type-class constraint; the value at position 1 does not.\n')); - - throws(function() { S.xor(S.Right(42), S.Right(43)); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) Right(42) :: Either ??? Number, Either ??? FiniteNumber, Either ??? NonZeroFiniteNumber, Either ??? Integer, Either ??? ValidNumber\n' + - '\n' + - '‘xor’ requires ‘a’ to satisfy the Monoid type-class constraint; the value at position 1 does not.\n')); - }); - - it('throws if applied to values of different types', function() { - throws(function() { S.xor([], false); }, - errorEq(TypeError, - 'Type-variable constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^ ^\n' + - ' 1 2\n' + - '\n' + - '1) [] :: Array ???\n' + - '\n' + - '2) false :: Boolean\n' + - '\n' + - 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); - - throws(function() { S.xor(R.__, false)([]); }, - errorEq(TypeError, - 'Type-variable constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^ ^\n' + - ' 1 2\n' + - '\n' + - '1) [] :: Array ???\n' + - '\n' + - '2) false :: Boolean\n' + - '\n' + - 'Since there is no type of which all the above values are members, the type-variable constraint has been violated.\n')); - }); - - it('throws if applied to values without a "toBoolean" method', function() { - throws(function() { S.xor(0, 1); }, - errorEq(TypeError, - 'Type-class constraint violation\n' + - '\n' + - 'xor :: (Alternative a, Monoid a) => a -> a -> a\n' + - ' ^^^^^^^^^^^^^ ^\n' + - ' 1\n' + - '\n' + - '1) 0 :: Number, FiniteNumber, Integer, ValidNumber\n' + - '\n' + - '‘xor’ requires ‘a’ to satisfy the Alternative type-class constraint; the value at position 1 does not.\n')); - }); - -});