Repository: sanctuary-js/sanctuary (js)

Issue:
S.get and S.gets without first argument
Hello there, I'm relatively new at FP in JS and i'm finding this library quire useful.

I am a heavy Ramda user and two of the functions I use most are [`R.prop`](https://ramdajs.com/docs/#prop) and [`R.path`](https://ramdajs.com/docs/#path). 

What I really like about both of them is that they allow me to get a value in a safe way.
If the prop or the path is missing in the provided object, `undefined` is returned.

It would be perfect if:
1) the prop/path exists, a `Just(value)` is returned
2) the prop/path does not exists a `Nothing` is returned

Your library implements an `S.prop` and an `S.props` having a different behavior.
Indeed in case 1 the value is returned, while in case 2 an error is thrown.

So in your documentation it is suggested to use the `S.get`/`S.gets` functions to access a property/path of uncertain objects.
But in such a case the functions signature is different because I need - first of all - to provide a predicate.

Eventually I ended up to use a combination of `monet.js` and `ramda` to obtain a function satisfying point 1 and 2 of my previous list.
For example, this is a possible solution (where R is ramda and Maybe is from monet). 

```
const propMaybe = prop => R.pipe(
  R.prop(prop),
  Maybe.fromNull,
)
```

As I said at the beginning, I am quite new in the FP world, so I am sure I am missing the point. But wouldn't be useful a something like the function I described?

I think you have already something very similar that is `S.head`.

Thank you in advance for your answers.

Pull request description:
strmap: add S.value
Closes #611

Please let me know if you prefer `lookup` or some other name.

/cc @daaaaa


Patch:
diff --git a/index.js b/index.js
index 0896785..9ca44f5 100644
--- a/index.js
+++ b/index.js
@@ -487,11 +487,6 @@
     };
   }
 
-  //  value :: { value :: a } -> a
-  function value(r) {
-    return r.value;
-  }
-
   //  :: Type
   var a = $.TypeVariable ('a');
   var b = $.TypeVariable ('b');
@@ -2283,7 +2278,7 @@
   //. ['foo', 'baz']
   //. ```
   function justs(maybes) {
-    return map (value) (filter (isJust) (maybes));
+    return map (prop ('value')) (filter (isJust) (maybes));
   }
   _.justs = {
     consts: {f: [Z.Filterable, Z.Functor]},
@@ -2557,7 +2552,7 @@
   _.lefts = {
     consts: {f: [Z.Filterable, Z.Functor]},
     types: [f ($Either (a) (b)), f (a)],
-    impl: B (map (value)) (filter (isLeft))
+    impl: B (map (prop ('value'))) (filter (isLeft))
   };
 
   //# rights :: (Filterable f, Functor f) => f (Either a b) -> f b
@@ -2574,7 +2569,7 @@
   _.rights = {
     consts: {f: [Z.Filterable, Z.Functor]},
     types: [f ($Either (a) (b)), f (b)],
-    impl: B (map (value)) (filter (isRight))
+    impl: B (map (prop ('value'))) (filter (isRight))
   };
 
   //# tagBy :: (a -> Boolean) -> a -> Either a a
@@ -3830,6 +3825,7 @@
   //. lacks the specified property, a type error is thrown.
   //.
   //. For accessing properties of uncertain objects, use [`get`](#get) instead.
+  //. For accessing string map values by key, use [`value`](#value) instead.
   //.
   //. ```javascript
   //. > S.prop ('a') ({a: 1, b: 2})
@@ -3884,7 +3880,7 @@
   //. value of the specified object property if it exists and the value
   //. satisfies the given predicate; Nothing otherwise.
   //.
-  //. See also [`gets`](#gets) and [`prop`](#prop).
+  //. See also [`gets`](#gets), [`prop`](#prop), and [`value`](#value).
   //.
   //. ```javascript
   //. > S.get (S.is ($.Number)) ('x') ({x: 1, y: 2})
@@ -3952,6 +3948,38 @@
   //. [type identifier][] is `'Object'` and the values of its enumerable own
   //. properties are all members of type `a`.
 
+  //# value :: String -> StrMap a -> Maybe a
+  //.
+  //. Retrieve the value associated with the given key in the given string map.
+  //.
+  //. Formally, `value (k) (m)` evaluates to `Just (m[k])` if `k` is an
+  //. enumerable own property of `m`; `Nothing` otherwise.
+  //.
+  //. See also [`prop`](#prop) and [`get`](#get).
+  //.
+  //. ```javascript
+  //. > S.value ('foo') ({foo: 1, bar: 2})
+  //. Just (1)
+  //.
+  //. > S.value ('bar') ({foo: 1, bar: 2})
+  //. Just (2)
+  //.
+  //. > S.value ('baz') ({foo: 1, bar: 2})
+  //. Nothing
+  //. ```
+  function value(key) {
+    return function(strMap) {
+      return Object.prototype.propertyIsEnumerable.call (strMap, key) ?
+             Just (strMap[key]) :
+             Nothing;
+    };
+  }
+  _.value = {
+    consts: {},
+    types: [$.String, $.StrMap (a), $Maybe (a)],
+    impl: value
+  };
+
   //# singleton :: String -> a -> StrMap a
   //.
   //. Takes a string and a value of any type, and returns a string map with


Test patch:
diff --git a/test/internal/strMap.js b/test/internal/strMap.js
new file mode 100644
index 0000000..1175e81
--- /dev/null
+++ b/test/internal/strMap.js
@@ -0,0 +1,29 @@
+'use strict';
+
+const proto = Object.create (null);
+
+Object.defineProperty (
+  proto,
+  'non-enumerable inherited property',
+  {enumerable: false, value: 'non-enumerable inherited property'}
+);
+Object.defineProperty (
+  proto,
+  'enumerable inherited property',
+  {enumerable: true, value: 'enumerable inherited property'}
+);
+
+const strMap = Object.create (proto);
+
+Object.defineProperty (
+  strMap,
+  'non-enumerable own property',
+  {enumerable: false, value: 'non-enumerable own property'}
+);
+Object.defineProperty (
+  strMap,
+  'enumerable own property',
+  {enumerable: true, value: 'enumerable own property'}
+);
+
+module.exports = strMap;
diff --git a/test/keys.js b/test/keys.js
index 68dc146..06a7a27 100644
--- a/test/keys.js
+++ b/test/keys.js
@@ -3,6 +3,7 @@
 const S = require ('..');
 
 const eq = require ('./internal/eq');
+const strMap = require ('./internal/strMap');
 
 
 test ('keys', () => {
@@ -14,11 +15,6 @@ test ('keys', () => {
   eq (S.sort (S.keys ({}))) ([]);
   eq (S.sort (S.keys ({a: 1, b: 2, c: 3}))) (['a', 'b', 'c']);
 
-  const proto = {a: 1, b: 2};
-  const obj = Object.create (proto);
-  obj.c = 3;
-  obj.d = 4;
-
-  eq (S.sort (S.keys (obj))) (['c', 'd']);
+  eq (S.keys (strMap)) (['enumerable own property']);
 
 });
diff --git a/test/pairs.js b/test/pairs.js
index 33da1d3..d2251ab 100644
--- a/test/pairs.js
+++ b/test/pairs.js
@@ -3,6 +3,7 @@
 const S = require ('..');
 
 const eq = require ('./internal/eq');
+const strMap = require ('./internal/strMap');
 
 
 test ('pairs', () => {
@@ -14,11 +15,6 @@ test ('pairs', () => {
   eq (S.sort (S.pairs ({}))) ([]);
   eq (S.sort (S.pairs ({a: 1, b: 2, c: 3}))) ([S.Pair ('a') (1), S.Pair ('b') (2), S.Pair ('c') (3)]);
 
-  const proto = {a: 1, b: 2};
-  const obj = Object.create (proto);
-  obj.c = 3;
-  obj.d = 4;
-
-  eq (S.sort (S.pairs (obj))) ([S.Pair ('c') (3), S.Pair ('d') (4)]);
+  eq (S.pairs (strMap)) ([S.Pair ('enumerable own property') ('enumerable own property')]);
 
 });
diff --git a/test/value.js b/test/value.js
new file mode 100644
index 0000000..1fbb160
--- /dev/null
+++ b/test/value.js
@@ -0,0 +1,26 @@
+'use strict';
+
+const S = require ('..');
+
+const eq = require ('./internal/eq');
+const strMap = require ('./internal/strMap');
+
+
+test ('value', () => {
+
+  eq (typeof S.value) ('function');
+  eq (S.value.length) (1);
+  eq (S.show (S.value)) ('value :: String -> StrMap a -> Maybe a');
+
+  eq (S.value ('foo') ({foo: 1, bar: 2})) (S.Just (1));
+  eq (S.value ('bar') ({foo: 1, bar: 2})) (S.Just (2));
+  eq (S.value ('baz') ({foo: 1, bar: 2})) (S.Nothing);
+
+  eq (S.value ('valueOf') ({})) (S.Nothing);
+
+  eq (S.value ('non-enumerable inherited property') (strMap)) (S.Nothing);
+  eq (S.value ('enumerable inherited property') (strMap)) (S.Nothing);
+  eq (S.value ('non-enumerable own property') (strMap)) (S.Nothing);
+  eq (S.value ('enumerable own property') (strMap)) (S.Just ('enumerable own property'));
+
+});
diff --git a/test/values.js b/test/values.js
index abf5a5c..0ff1de4 100644
--- a/test/values.js
+++ b/test/values.js
@@ -3,6 +3,7 @@
 const S = require ('..');
 
 const eq = require ('./internal/eq');
+const strMap = require ('./internal/strMap');
 
 
 test ('values', () => {
@@ -14,11 +15,6 @@ test ('values', () => {
   eq (S.sort (S.values ({}))) ([]);
   eq (S.sort (S.values ({a: 1, b: 2, c: 3}))) ([1, 2, 3]);
 
-  const proto = {a: 1, b: 2};
-  const obj = Object.create (proto);
-  obj.c = 3;
-  obj.d = 4;
-
-  eq (S.sort (S.values (obj))) ([3, 4]);
+  eq (S.values (strMap)) (['enumerable own property']);
 
 });