0

I'm trying to write a library where the input is an Array of functions where the output of each function is merged in with its input and then passed into the next function.

Basically similar to compose/flow/pipe, but the input is always a single object and the output only specifies new properties.

EG:

(A) -> B, 
(A & B) -> C,
(A & B & C) -> D
...

I was able to accomplish this, but I'm sure there must be a "cleaner" more functional way to do it with fp-ts:

Working Example

NOTES:

  • The caller cannot be responsible for the "merging" the input and output. I need a interface that accepts the collection of functions in the form where each only returns it's component part.
  • The input of functions must be type-safe, and ideally forgiving (declaring function that takes (A & B & C) with only (A & C) should not throw a type error.
NSjonas
  • 10,693
  • 9
  • 66
  • 92
  • 1
    Typing `flow` with no overloads is already kind of hard and ugly, and you are still limited to a certain number of generics, but this sliding window of intersections on top would most probably mess up type inference. I think you should stick with this implementation. You may be able to factorise some code but that's about it. – geoffrey Mar 13 '23 at 01:17

1 Answers1

1

If I understand your code correctly, you're looking for the so-called Do notation (or bind/bindTo, take your pick of what exactly to use) with the Identity monad (since you aren't working with Option/Either/etc.).

const y = mergeFlow(
  (ctx: { input: string }) => ({ length: ctx.input.length }),
  ({ length }) => ({ isTooLong: length > 5 }),
  ({ isTooLong, input }) => ({
    trimmed: isTooLong ? input.slice(0, 5) : input
  }),
  ({ trimmed, length }) => ({ difference: length - trimmed.length })
);

would be

import { bind, bindTo } from 'fp-ts/Identity'

const x = (ctx: { input: string }) => pipe(
  ctx,
  bindTo('ctx'),
  bind('length', ({ ctx }) => ctx.input.length),
  bind('isTooLong', ({ length }) => length > 5),
  bind('trimmed', ({ isTooLong, ctx: { input } }) => isTooLong ? input.slice(0, 5) : input),
  bind('difference', ({ length, trimmed }) => length - trimmed.length)
)

Note that if you could write this in TypeScript

bindTo('ctx')<{ctx: { input: string }}>,
bind(...)

then you could use flow instead of pipe and write this:

const x = flow(
  bindTo('ctx'),
  bind('length', ...),
  ...
)

and not bother with hard-coding the (ctx: { input: string }) => ... in the function definition.

In any case, type of x is

(ctx: { input: string }) => ({
  readnly ctx: { input: string },
  readonly length: number,
  readonly isTooLong: boolean,
  readonly trimmed: string,
  readonly difference: string,
})

Because the keys are readonly, you shouldn't be able to modify any of them, only add new props like you asked.

user1713450
  • 1,307
  • 7
  • 18