Fp-ts: Nested pipes

Created on 21 Oct 2019  路  8Comments  路  Source: gcanti/fp-ts

馃摉 Documentation

For peace of mind, I would love to know whether nested pipes are ok or not.

export const deleteContent: Endomorphism<StateWithSelection> = state =>
  pipe(
    state,
    // state.element
    elementLens.modify(
      pipe(
        state.selection,
        mapSelectionToRange,
        deleteContentElement,
      ),
    ),
    select(collapseSelectionToStart(state.selection)),
  );
question

Most helpful comment

@pfgray Well, I wouldn't agree with that rule of thumb. IMO it bloats API and increases maintenance complexity. If you ever need to get access to hidden argument (for example to parametrise some operator in the flow) you end either adding extra lambdas or rewriting flow to pipe. This in turn leads to a messy code filled with a mix of flow and pipe.
On the other hand after migration to classless/pipeable an idiomatic and universal way to work with fp-ts is to use pipe.
We've been battle-testing fp-ts since 0.x with different teams on different projects and API surface has always been a problem for us in terms of documentation, best practices, idiomatic approaches etc. Therefore I would recommend to always stick to pipe because it's universal and to use flow if _and only if_ you know what you are doing and have strong pros for it besides point-free and code size.

All 8 comments

This looks weird as well. It is idiomatic fp-ts code or not?

export const select = (selection: Selection): Endomorphism<Value> => value =>
  pipe(
    value.selection,
    exists(s => eqSelection.equals(s, selection)),
  )
    ? value
    : { ...value, selection: some(selection) };

I am writing this question here in order to improve docs. I will send PR once I will understand it.

I am pretty sure I am doing something wrong. But what?

export const setText = (
  text: string,
  selection?: Selection,
): Endomorphism<Value> => value =>
  pipe(
    fromNullable(selection),
    chain(() => value.selection),
    fold(
      () => value,
      selection =>
        pipe(
          value,
          elementLens.modify(setTextElement(text, selection)),
        ),
    ),
  );

How to kill this pyramid of doom?

export const deleteContent: Endomorphism<Value> = value =>
  pipe(
    value.selection,
    fold(
      () => value,
      selection =>
        pipe(
          value,
          elementLens.modify(
            pipe(
              selection,
              mapSelectionToRange,
              deleteContentElement,
            ),
          ),
          select(collapseSelectionToStart(selection)),
        ),
    ),
  );

If I'm not mistaken, setText and deleteContent can be refactored by removing the fold and replacing it with a map and then getOrElse

export const setText = (
  text: string,
  selection?: Selection,
): Endomorphism<Value> => value =>
  pipe(
    fromNullable(selection),
    chain(() => value.selection),
    map(selection =>
      pipe(
        value,
        elementLens.modify(setTextElement(text, selection)),
      )),
    getOrElse(() => value)
  );

Anyway, you still need the other pipes. In order to improve the readability and expressiveness of the code you can extract some of the "nested" logic into functions.

const modifyElementText = (text: string, value: Value) => (s: Selection): Selection => 
  pipe(
    value,
    elementLens.modify(setTextElement(text, selection)),
  )

export const setText = (
  text: string,
  selection?: Selection,
): Endomorphism<Value> => value =>
  pipe(
    fromNullable(selection),
    chain(() => value.selection),
    map(modifyElementText(text, value)),
    getOrElse(() => value)
  );

Note that:

a => pipe(a, ...)

is the same as:

flow(...)

The rule of thumb is that if a can be inferred, you can probably substitute pipe for flow.

@pfgray Well, I wouldn't agree with that rule of thumb. IMO it bloats API and increases maintenance complexity. If you ever need to get access to hidden argument (for example to parametrise some operator in the flow) you end either adding extra lambdas or rewriting flow to pipe. This in turn leads to a messy code filled with a mix of flow and pipe.
On the other hand after migration to classless/pipeable an idiomatic and universal way to work with fp-ts is to use pipe.
We've been battle-testing fp-ts since 0.x with different teams on different projects and API surface has always been a problem for us in terms of documentation, best practices, idiomatic approaches etc. Therefore I would recommend to always stick to pipe because it's universal and to use flow if _and only if_ you know what you are doing and have strong pros for it besides point-free and code size.

I got it. Thank you for the answers.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

vicrac picture vicrac  路  4Comments

jollytoad picture jollytoad  路  4Comments

steida picture steida  路  4Comments

josete89 picture josete89  路  3Comments

Crashthatch picture Crashthatch  路  4Comments