1

Using fp-ts. I have an option of an array

const arrayofKeys: Option<Array<K>>, 

and an option of a record

const record: Option<Record<K,V>>

I want to pick the Vs of the Record where Ks intersect with the Array and stick the result in an Option.

In ramda: R.pick(arrayOfKeys, record)

How do i solve this with fp-ts or other packages within the fp-ts ecosystem?

Simon
  • 621
  • 4
  • 21

3 Answers3

4

I'd personally avoid Ramda et al as in my experience they're not very well typed. Here's a pure fp-ts approach (Str.fromNumber is from fp-ts-std, trivially replaced):

declare const arrayOfKeyNums: Option<Array<number>>
const arrayOfKeys = pipe(arrayOfKeyNums, O.map(A.map(Str.fromNumber)))
declare const record: Option<Record<string, number>>

const keyIntersectedVals: O.Option<Array<number>> = pipe(
  sequenceT(O.Apply)(arrayOfKeys, record),
  O.map(([ks, rec]) =>
    pipe(
      rec,
      R.foldMapWithIndex(Str.Ord)(A.getMonoid<number>())((k, v) =>
        A.elem(Str.Eq)(k)(ks) ? [v] : [],
      ),
    ),
  ),
)

It's a bit verbose owing to the need to pass typeclass instances around. On the plus side, the use of typeclass instances means that this can be trivially updated to support any value type, including non-primitive types with any given Eq.

Here's what the body might instead look like in Haskell for comparison, where typeclass instances don't need to be passed around:

keyIntersectedVals :: Maybe [Int]
keyIntersectedVals = uncurry (M.foldMapWithKey . intersectedToList) <$> sequenceT (mkeys, mmap)
  where intersectedToList ks k v
          | k `elem` ks = [v]
          | otherwise   = []

For example, given keys O.some(["a", "c"]) and a record O.some({ a: 123, b: 456, c: 789 }), we get O.some([123, 789]).

Sam A. Horvath-Hunt
  • 931
  • 1
  • 7
  • 20
3

Ramda's lift lifts a function on some values to work on a container of those values. So lift (pick) will likely do what you want, so long as fp-ts's Option supports the FantasyLand Apply specification.

const {of} = folktale.maybe
const {lift, pick} = R

const keys = of (['k', 'e', 'y', 's'])  // Maybe (['k', 'e', 'y', 's'])
const record = of ({s: 1, k: 2, y: 3, b: 4, l: 5, u: 6, e: 7}) // Maybe ({s: 1, k: 2, ...})

console .log (lift (pick) (keys, record) .toString())
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.28.0/ramda.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/folktale/2.0.0/folktale.min.js"></script>
Scott Sauyet
  • 49,207
  • 4
  • 49
  • 103
0

This is a great use case for traverseArray, an optimized version of traverse. You can also use "Do notation" and apS to get a really clean, monadic pipeline. If any of these operations return a None, the entire flow will terminate early (this is a good!).

Also, lookup is a very handy function similar to get from Ramda/Lodash, but it returns an Option. Both the Record and Array modules export a version of this function.

declare const arrayofKeys: O.Option<Array<string>>
declare const record: O.Option<Record<string, number>>

export const result: O.Option<ReadonlyArray<number>> = pipe(
  O.Do,
  O.apS('keys', arrayofKeys),
  O.apS('rec', record),
  O.chain(({ keys, rec }) =>
    pipe(
      keys,
      O.traverseArray(key => pipe(rec, R.lookup(key)))
    )
  )
)

Functions used:

  1. https://gcanti.github.io/fp-ts/modules/Option.ts.html#do
  2. https://gcanti.github.io/fp-ts/modules/Option.ts.html#aps
  3. https://gcanti.github.io/fp-ts/modules/Option.ts.html#chain
  4. https://gcanti.github.io/fp-ts/modules/Option.ts.html#traversearray
  5. https://gcanti.github.io/fp-ts/modules/Record.ts.html#lookup
newswim
  • 430
  • 3
  • 7