Repository: sanctuary-js/sanctuary
Pull request #698 (2020-08-26 10:43:49)

array: add S.findMap
Supersedes #697

With [`find`][1] one sometimes applies a transformation to determine whether an element is suitable only to apply the transformation a second time if a suitable element is found:

```javascript
> S.chain (S.parseInt (16))
.         (S.find (S.compose (S.isJust) (S.parseInt (16)))
.                 (['A', 'B', 'C']))
Just (10)
```

With `findMap` the transformation is provided just once:

```javascript
> S.findMap (S.parseInt (16)) (['A', 'B', 'C'])
Just (10)
```


[1]: https://sanctuary.js.org/#find



Patch:
diff --git a/index.js b/index.js
index f920ebe..a216c34 100644
--- a/index.js
+++ b/index.js
@@ -3204,7 +3204,7 @@
   //. of the structure that satisfies the predicate; Nothing if there is no
   //. such element.
   //.
-  //. See also [`elem`](#elem).
+  //. See also [`findMap`](#findMap) and [`elem`](#elem).
   //.
   //. ```javascript
   //. > S.find (S.lt (0)) ([1, -2, 3, -4, 5])
@@ -3214,20 +3214,46 @@
   //. Nothing
   //. ```
   function find(pred) {
+    return findMap (function(x) { return pred (x) ? Just (x) : Nothing; });
+  }
+  _.find = {
+    consts: {f: [Z.Foldable]},
+    types: [$.Predicate (a), f (a), $.Maybe (a)],
+    impl: find
+  };
+
+  //# findMap :: Foldable f => (a -> Maybe b) -> f a -> Maybe b
+  //.
+  //. Finds the leftmost element of the given structure for which the given
+  //. function returns a Just, and returns that Just (or Nothing if none of
+  //. the elements matches).
+  //.
+  //. More flexible than [`find`](#find), and more convenient in situations
+  //. in which the result of a successful computation can be reused.
+  //.
+  //. ```javascript
+  //. > S.findMap (S.parseInt (16)) ([])
+  //. Nothing
+  //.
+  //. > S.findMap (S.parseInt (16)) (['X', 'Y', 'Z'])
+  //. Nothing
+  //.
+  //. > S.findMap (S.parseInt (16)) (['A', 'B', 'C'])
+  //. Just (10)
+  //. ```
+  function findMap(f) {
     return function(xs) {
       return Z.reduce (
-        function(m, x) {
-          return m.isJust ? m : pred (x) ? Just (x) : Nothing;
-        },
+        function(m, x) { return m.isJust ? m : f (x); },
         Nothing,
         xs
       );
     };
   }
-  _.find = {
+  _.findMap = {
     consts: {f: [Z.Foldable]},
-    types: [$.Predicate (a), f (a), $.Maybe (a)],
-    impl: find
+    types: [$.Fn (a) ($.Maybe (b)), f (a), $.Maybe (b)],
+    impl: findMap
   };
 
   //# intercalate :: (Monoid m, Foldable f) => m -> f m -> m


Test patch:
diff --git a/test/findMap.js b/test/findMap.js
new file mode 100644
index 0000000..bce0dc2
--- /dev/null
+++ b/test/findMap.js
@@ -0,0 +1,19 @@
+'use strict';
+
+const S = require ('..');
+
+const eq = require ('./internal/eq');
+
+
+test ('findMap', () => {
+
+  eq (S.show (S.findMap)) ('findMap :: Foldable f => (a -> Maybe b) -> f a -> Maybe b');
+
+  eq (S.findMap (S.parseInt (16)) ([])) (S.Nothing);
+  eq (S.findMap (S.parseInt (16)) (['H', 'G'])) (S.Nothing);
+  eq (S.findMap (S.parseInt (16)) (['H', 'G', 'F', 'E'])) (S.Just (15));
+  eq (S.findMap (S.parseInt (16)) ({})) (S.Nothing);
+  eq (S.findMap (S.parseInt (16)) ({a: 'H', b: 'G'})) (S.Nothing);
+  eq (S.findMap (S.parseInt (16)) ({a: 'H', b: 'G', c: 'F', d: 'E'})) (S.Just (15));
+
+});