← Назад к вопросам

Реализовать функцию pipe и compose

1.8 Middle🔥 81 комментариев
#JavaScript Core#Архитектура и паттерны

Условие

Напишите функции pipe и compose для композиции функций.

Требования

  1. pipe(f1, f2, f3)(x) эквивалентно f3(f2(f1(x))) - слева направо
  2. compose(f1, f2, f3)(x) эквивалентно f1(f2(f3(x))) - справа налево
  3. Должны работать с любым количеством функций

Примеры

const add5 = x => x + 5;
const multiply2 = x => x * 2;
const subtract3 = x => x - 3;

const piped = pipe(add5, multiply2, subtract3);
piped(10); // ((10 + 5) * 2) - 3 = 27

const composed = compose(subtract3, multiply2, add5);
composed(10); // (10 + 5) * 2 - 3 = 27

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение

Задача на pipe и compose — демонстрирует функциональное программирование и преобразование функций.

Что такое pipe и compose?

pipe — слева направо

  • pipe(f, g, h)(x) = h(g(f(x)))

compose — справа налево

  • compose(f, g, h)(x) = f(g(h(x)))

Решение 1: Базовые версии

const pipe = (...fns) => (x) =>
  fns.reduce((acc, fn) => fn(acc), x);

const compose = (...fns) => (x) =>
  fns.reduceRight((acc, fn) => fn(acc), x);

// Использование
const add5 = x => x + 5;
const multiply2 = x => x * 2;
const subtract3 = x => x - 3;

const piped = pipe(add5, multiply2, subtract3);
console.log(piped(10)); // ((10 + 5) * 2) - 3 = 27

const composed = compose(subtract3, multiply2, add5);
console.log(composed(10)); // ((10 + 5) * 2) - 3 = 27

Решение 2: С типизацией TypeScript

type Fn<T, R> = (arg: T) => R;

function pipe<T, R>(...fns: Fn<any, any>[]): (arg: T) => R {
  return (x: T) => fns.reduce((acc, fn) => fn(acc), x);
}

function compose<T, R>(...fns: Fn<any, any>[]): (arg: T) => R {
  return (x: T) => fns.reduceRight((acc, fn) => fn(acc), x);
}

// Использование с типами
const add5 = (x: number): number => x + 5;
const multiply2 = (x: number): number => x * 2;
const subtract3 = (x: number): number => x - 3;

const piped = pipe(add5, multiply2, subtract3);
const result: number = piped(10); // Type-safe!

Решение 3: С проверкой ошибок

const pipe = (...fns) => {
  if (fns.length === 0) {
    throw new Error("pipe requires at least one function");
  }

  return (x) => {
    try {
      return fns.reduce((acc, fn) => {
        if (typeof fn !== "function") {
          throw new Error("All arguments to pipe must be functions");
        }
        return fn(acc);
      }, x);
    } catch (error) {
      console.error("Error in pipe:", error);
      throw error;
    }
  };
};

const compose = (...fns) => {
  if (fns.length === 0) {
    throw new Error("compose requires at least one function");
  }

  return (x) => {
    try {
      return fns.reduceRight((acc, fn) => {
        if (typeof fn !== "function") {
          throw new Error("All arguments to compose must be functions");
        }
        return fn(acc);
      }, x);
    } catch (error) {
      console.error("Error in compose:", error);
      throw error;
    }
  };
};

Решение 4: С поддержкой асинхронных функций

const asyncPipe = (...fns) => async (x) => {
  let result = x;
  for (const fn of fns) {
    result = await fn(result);
  }
  return result;
};

const asyncCompose = (...fns) => async (x) => {
  let result = x;
  for (let i = fns.length - 1; i >= 0; i--) {
    result = await fns[i](result);
  }
  return result;
};

// Использование с async функциями
const fetchUser = async (id) => {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
};

const formatName = async (user) => ({
  ...user,
  name: user.name.toUpperCase()
});

const piped = asyncPipe(fetchUser, formatName);
const user = await piped(123);

Решение 5: Практический пример

// Функции для работы со строками
const trim = str => str.trim();
const toLowerCase = str => str.toLowerCase();
const split = separator => str => str.split(separator);
const filter = predicate => arr => arr.filter(predicate);
const join = separator => arr => arr.join(separator);

// Создаём pipeline обработки текста
const processText = pipe(
  trim,
  toLowerCase,
  split(/\s+/),
  filter(word => word.length > 2),
  join(" ")
);

const result = processText("  HELLO   WORLD  WI  ");
console.log(result); // "hello world"

Решение 6: С кэшированием промежуточных результатов

const cachedPipe = (...fns) => {
  const cache = new Map();

  return (x) => {
    const key = JSON.stringify(x);
    if (cache.has(key)) {
      return cache.get(key);
    }

    const result = fns.reduce((acc, fn) => fn(acc), x);
    cache.set(key, result);
    return result;
  };
};

Сравнение подходов

ВариантПростотаAsyncКешТипыРекомендация
Базовый✅✅НетНетНет✅ Best
TypeScriptНетНет✅✅Production
С проверкойНетНетНетDefensive
Async📊✅✅НетНетДля async
ПрактическийНетНетНетReal-world

Real-world пример: Обработка данных API

const getJSON = response => response.json();
const filterActive = users => users.filter(u => u.active);
const sortByName = users => users.sort((a, b) => a.name.localeCompare(b.name));
const extractNames = users => users.map(u => u.name);

const getActiveUserNames = pipe(
  fetch,
  getJSON,
  filterActive,
  sortByName,
  extractNames
);

const names = await getActiveUserNames('/api/users');

Best Practices

  1. reduce/reduceRight — основа реализации
  2. Однострочные функции — идеальны для pipe/compose
  3. Типизация — использовать TypeScript
  4. Async support — для работы с promises
  5. Обработка ошибок — оборачивать в try-catch

Рекомендации для собеседования

  1. Начните с базовых версий (reduce/reduceRight)
  2. Объясните разницу между pipe и compose
  3. Покажите практический пример
  4. Добавьте обработку ошибок
  5. Упомяните async версии

Итог: Базовые версии просты и понятны. TypeScript версия лучше для production.

Реализовать функцию pipe и compose | PrepBro