diff --git a/index.js b/index.js index 4dea6277..102e69d0 100644 --- a/index.js +++ b/index.js @@ -336,6 +336,21 @@ }); }; + // prop :: Accessible a => String -> a -> b + var prop = + def('prop', + {a: [Accessible]}, + [$.String, a, b], + function(key, obj) { + var boxed = Object(obj); + if (key in boxed) { + return boxed[key]; + } else { + throw new TypeError('‘prop’ expected object to have a property ' + + 'named ‘' + key + '’; ' + R.toString(obj) + ' does not'); + } + }); + //. ### Classify //# type :: a -> String @@ -1007,7 +1022,7 @@ method('Maybe#toBoolean', {}, [$Maybe(a), $.Boolean], - R.prop('isJust')); + prop('isJust')); //# Maybe#toString :: Maybe a ~> String //. @@ -1094,7 +1109,7 @@ def('isNothing', {}, [$Maybe(a), $.Boolean], - R.prop('isNothing')); + prop('isNothing')); //# isJust :: Maybe a -> Boolean //. @@ -1111,7 +1126,7 @@ def('isJust', {}, [$Maybe(a), $.Boolean], - R.prop('isJust')); + prop('isJust')); //# fromMaybe :: a -> Maybe a -> a //. @@ -1515,7 +1530,7 @@ method('Either#toBoolean', {}, [$Either(a, b), $.Boolean], - R.prop('isRight')); + prop('isRight')); //# Either#toString :: Either a b ~> String //. @@ -1604,7 +1619,7 @@ def('isLeft', {}, [$Either(a, b), $.Boolean], - R.prop('isLeft')); + prop('isLeft')); //# isRight :: Either a b -> Boolean //. @@ -1621,7 +1636,7 @@ def('isRight', {}, [$Either(a, b), $.Boolean], - R.prop('isRight')); + prop('isRight')); //# either :: (a -> c) -> (b -> c) -> Either a b -> c //. @@ -1698,7 +1713,7 @@ //. > S.encaseEither(S.I, JSON.parse, '[') //. Left(new SyntaxError('Unexpected end of input')) //. - //. > S.encaseEither(R.prop('message'), JSON.parse, '[') + //. > S.encaseEither(S.prop('message'), JSON.parse, '[') //. Left('Unexpected end of input') //. ``` S.encaseEither = @@ -2369,6 +2384,20 @@ //. ### Object + //# prop :: Accessible a => String -> a -> b + //. + //. Takes a property name and an object with known properties and returns + //. the value of the specified property. If for some reason the record + //. lacks the specified property, a type error is thrown. + //. + //. For accessing properties of uncertain objects, use [`get`](#get) instead. + //. + //. ```javascript + //. > S.prop('a', {a: 1, b: 2}) + //. 1 + //. ``` + S.prop = prop; + //# get :: Accessible a => TypeRep b -> String -> a -> Maybe b //. //. Takes a [type representative](#type-representatives), a property @@ -2379,7 +2408,7 @@ //. The `Object` type representative may be used as a catch-all since most //. values have `Object.prototype` in their prototype chains. //. - //. See also [`gets`](#gets). + //. See also [`gets`](#gets) and [`prop`](#prop). //. //. ```javascript //. > S.get(Number, 'x', {x: 1, y: 2}) diff --git a/test/index.js b/test/index.js index 04082a2e..baa85c12 100644 --- a/test/index.js +++ b/test/index.js @@ -2089,7 +2089,7 @@ describe('either', function() { }); it('applies the first argument to the Error', function() { - eq(S.encaseEither(R.prop('message'), factorial, -1), + eq(S.encaseEither(S.prop('message'), factorial, -1), S.Left('Cannot determine factorial of negative number')); }); @@ -2135,7 +2135,7 @@ describe('either', function() { }); it('applies the first argument to the Error', function() { - eq(S.encaseEither2(R.prop('message'), rem, 42, 0), + eq(S.encaseEither2(S.prop('message'), rem, 42, 0), S.Left('Cannot divide by zero')); }); @@ -2182,7 +2182,7 @@ describe('either', function() { }); it('applies the first argument to the Error', function() { - eq(S.encaseEither3(R.prop('message'), area, 2, 2, 5), + eq(S.encaseEither3(S.prop('message'), area, 2, 2, 5), S.Left('Impossible triangle')); }); @@ -3330,6 +3330,67 @@ describe('list', function() { describe('object', function() { + describe('prop', function() { + + it('is a binary function', function() { + eq(typeof S.prop, 'function'); + eq(S.prop.length, 2); + }); + + it('type checks its arguments', function() { + assert.throws(function() { S.prop(1); }, + errorEq(TypeError, + '‘prop’ expected a value of type String ' + + 'as its first argument; received 1')); + + assert.throws(function() { S.prop('a', null); }, + errorEq(TypeError, + '‘prop’ requires ‘a’ to implement Accessible; ' + + 'Null does not')); + + assert.throws(function() { S.prop('a', true); }, + errorEq(TypeError, + '‘prop’ expected object to have a property ' + + 'named ‘a’; true does not')); + + assert.throws(function() { S.prop('a', 1); }, + errorEq(TypeError, + '‘prop’ expected object to have a property ' + + 'named ‘a’; 1 does not')); + + }); + + it('throws when the key is not present', function() { + assert.throws(function() { S.prop('map', 'abcd'); }, + errorEq(TypeError, + '‘prop’ expected object to have a property ' + + 'named ‘map’; "abcd" does not')); + + assert.throws(function() { S.prop('c', {a: 0, b: 1}); }, + errorEq(TypeError, + '‘prop’ expected object to have a property ' + + 'named ‘c’; {"a": 0, "b": 1} does not')); + + assert.throws(function() { S.prop('xxx', [1, 2, 3]); }, + errorEq(TypeError, + '‘prop’ expected object to have a property ' + + 'named ‘xxx’; [1, 2, 3] does not')); + }); + + it('it returns the value of the specified object property', function() { + eq(S.prop('a', {a: 0, b: 1}), 0); + eq(S.prop('0', [1, 2, 3]), 1); + eq(S.prop('length', 'abc'), 3); + eq(S.prop('x', Object.create({x: 1, y: 2})), 1); + }); + + it('is curried', function() { + eq(S.prop('a').length, 1); + eq(S.prop('a')({a: 0, b: 1}), 0); + }); + + }); + describe('get', function() { it('is a ternary function', function() {