Разная композиция функций

Часто путаю методы композиции функий compose() и pipe() в Ramda, а потом сижу и разбираюсь почему цепочка функций работает не так как я хочу. 😞

compose() выполняет функции справа налево, pipe() наоборот — слева направо.

Думаю, эта дезориентация пошла от реактовых компонент высшего порядка.

Я привык писать:

const enhance = compose(
  /* подключаю компоненту к стору */
  connect(mapStateToProps, mapDispatchToProps),
  /* добавляю некоторые обработчики событий */
  withHandlers(handlers),
  /* удаляю лишние данные */
  cleanProps()
);

/* оборачиваю компоненту */
export default enhance(MyDumbComponent);

Каждая компонента высшего порядка порождает соответствующую React-компонту в дереве узлов. Их иерархия в точности соответствует порядку их объявления.

Но когда я хочу создать функцию преобразования данных, используя Ramda, то должен перечислять действия в обратном порядке.

Например, вот функция, которая выдаёт уникальные символы в строке, отсортированные по алфавиту:

const uniqChars = R.pipe(
  R.split(''),
  R.uniq,
  R.sortBy(R.prop(0)),
  R.join('')
);

uniqChars('Hello world!'); // " !Hdelorw"

Если для композиции использовать метод compose(), то в этом примере сразу будет ошибка:

TypeError: "Hello world!" does not have a method named "join"

Так почему с функциями высшего порядка всё работает правильно?!

Разгадка заключается в том, что HoC не запускаются сразу, а оборачивают переданный им компонент в ещё один компонент. Получается, что MyDumbComponent оборачивается в компонент cleanProps, потом в withHandlers, и, наконец, в connect. А когда React будет рисовать этот “умный” компонент, то начнёт вызывать эту матрёшку с самого верхнего слоя.