Функциональный способ повторного использования переменных в трубе - PullRequest
2 голосов
/ 02 октября 2019

Используя функциональное программирование в javascript и машинописном тексте вместе с Рамдой, я часто нахожу себя пишущим код:

const myFun = c => {
  const myId = c.id

  const value = pipe(
    getAnotherOtherPropOfC,
    transformToArray,
    mapToSomething,
    filterSomething,
    // ... N other transformations
    // ok now I need myId and also the result of the previous function
    chainMyIdWithResultOfPreviousFunction(myId)
  )(c)

  return value
}

Обратите внимание на то, как создание const myId нарушает стиль без точек. Я хотел бы написать myFun, чтобы не было необходимости явно указывать c. Так что-то вроде: const myFun = pipe(....)

Мне было интересно, есть ли более функциональный и читаемый способ делать такие вещи.

Ответы [ 4 ]

4 голосов
/ 02 октября 2019

Можно ли это сделать? Конечно. Должно ли это сделать? Это не так ясно.

Вот бессмысленная версия вышеупомянутого, использующая lift:

const getAnotherOtherPropOfC = prop ('x')
const transformToArray = split (/,\s*/)
const mapToSomething = map (Number)
const filterSomething = filter (n => n % 2 == 1)
const square = (n) => n * n
const chainMyIdWithResultOfPreviousFunction = (id) => (arr) => `${id}: [${arr}]`

const myFun = lift (chainMyIdWithResultOfPreviousFunction) (
  prop ('id'),
  pipe(
    getAnotherOtherPropOfC,
    transformToArray,
    mapToSomething,
    filterSomething,
    map (square)
  )
)

console .log (
  myFun ({id: 'foo', x: '1, 2, 3, 4, 5'}) // => 'foo: [1,9,25]'
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
<script> const {prop, split, map, filter, lift, pipe} = R            </script>

lift - более стандартная функция FP, чем converge Рамды (которая вместе с useWith предлагают способы создания бессмысленных решений, часто за счет читабельности.) lift перекрывается с converge при применении к функциям, но предназначен для унарных функций, где обрабатывается convergeполиадические.

Это не ужасно. Но единственное преимущество, которое он имеет перед оригиналом, - это то, что он не требует очков. И если бы вы попытались расширить это до промежуточных функций в этом конвейере, это было бы совершенно безобразно.

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

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

Мой стандартный пример - const sum = reduce(add, 0) чище и понятнее, чем const sum = (xs) => xs.reduce(add, 0). Это также очень ясно показывает параллели с const product = reduce(multiply, 1) или const all = reduce(and, true). Но по мере того, как вы становитесь более сложными или когда вам необходимо повторно использовать промежуточные вычисления (как указал Берги), бессмысленный код часто становится пассивом.

У меня нет реального разговора о том,Версия является улучшением по сравнению с оригиналом. Если так, то это только второстепенный. Дальнейшая его переноска значительно ухудшит читабельность.

1 голос
/ 02 октября 2019

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

const myFun = c => {
    let id = c.id;

    return R.pipe(
        R.prop('list'),
        R.map(R.add(10)),
        R.sum,
        R.subtract(id),
    )(c)
}

может быть преобразован в две отдельные функции:

const compute = R.pipe(
    R.prop('list'),
    R.map(R.add(10)),
    R.sum,
);

const getId = R.prop('id');

, а затем просто

const myFun = c => getId(c) - compute(c)

, что выглядит достаточно хорошо для меня, ноесли вы хотите быть абсолютно без очков, то

const myFun = R.converge(R.subtract, [getId, compute])

детская площадка

0 голосов
/ 02 октября 2019

Как насчет этого:

const getAnotherPropOfC = R.prop('message');
const transformToArray = R.split('');
const mapToSomething = R.map(R.toUpper);
const filterSomething = R.reject(R.equals(' '));
const chainMyIdWithResultOfPreviousFunction = R.pipe(
  R.prop('fragment'), 
  R.concat,
);

const myFun = R.converge(R.map, [
  chainMyIdWithResultOfPreviousFunction,
  R.pipe(
    getAnotherPropOfC,
    transformToArray,
    mapToSomething,
    filterSomething,
  ),
]);



console.log(
  myFun({ message: 'Hello World', fragment: '!' }),
);
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
0 голосов
/ 02 октября 2019

Обратите внимание, что я использую vanilla JS, чтобы достичь более широкой аудитории.

A pipe - это просто составная функция, это просто функция. Итак, давайте используем канал более высокого порядка, который принимает канал, функцию и значение и возвращает другую функцию вместо результата. Чтобы получить результат, нам нужно дважды передать значение:

const toUc = s => s.toUpperCase();
const double = s => `${s}${s}`;
const shout = s => `${s}!`;
const concat = t => s => `${s} -> ${t}`;

const pipe = g => f => x => f(g(x));
const pipe3 = h => g => f => x => f(g(h(x)));

const foo = {id: "hi"};
const bar = pipe3(toUc) (shout) (double); // pipe

const baz = pipe(bar) (concat); // higher order pipe

// the result of the applied pipe is just another function because concat requires two arguments!

console.log(
  baz(foo.id)) // s => `${s} -> ${t}`

// let's apply it twice

console.log(
  baz(foo.id)
    (foo.id)); // hi -> HI!HI!
...