Делаем что-то бесточечное в Ramda

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

const filterPayments = filter => payments =>
  r.filter(
    r.allPass([
      typePred(filter),
      amountPred(filter),
      accountPred(filter),
      currencyPred(filter)
    ]),
    payments
  );

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

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

const amountPred = filter => p =>
  r.and(p.betrag >= filter.amount.min, p.betrag <= filter.amount.max);

Может быть, кто-нибудь может указать мне правильное направление или предложить некоторые рекомендации?


person Mauravan    schedule 29.06.2018    source источник


Ответы (2)


Я бы посоветовал придерживаться того, что у вас есть на данный момент :)

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

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

const filterPayments = filter =>
  R.filter(
    R.allPass([
      typePred(filter),
      amountPred(filter),
      accountPred(filter),
      currencyPred(filter)
    ]));

Далее мы можем рассмотреть список функций, переданных в R.allPass.

R.allPass(R.map(p => p(filter), [typePred, amountPred, accountPred, currencyPred]))

Теперь мы можем начать удалять аргумент filter, перевернув R.map, чтобы подтолкнуть его к концу.

R.allPass(R.flip(R.map)([typePred, amountPred, accountPred, currencyPred])(p => p(filter))

Теперь давайте сделаем filter и аргумент для p => p(filter) вместо того, чтобы закрывать его, сделав это:

R.allPass(R.flip(R.map)
  ([typePred, amountPred, accountPred, currencyPred])
  (R.applyTo(filter)))

Теперь это начинает обретать форму, как h(g(f(a))), которую мы можем преобразовать в бесточечную, используя R.pipe, R.compose, R.o и т. д.

const filterPayments = R.compose(
  R.filter,
  R.allPass,
  R.flip(R.map)([typePred, amountPred__, accountPred, currencyPred]),
  R.unary(R.applyTo) // R.unary is needed here to tell Ramda only to expect the first argument in this composition
)

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

Ваш пример amountPred становится еще более запутанным, и я думаю, что впоследствии вы согласитесь, что то, что у вас уже есть, является гораздо лучшим выбором между ними.

Опять же, мы начинаем с попытки поместить аргументы в конец каждой стороны выражения:

filter => p =>
  both(
    x => x >= filter.amount.min,
    x => x <= filter.amount.max
  )(p.betrag)

И тогда мы можем отбросить p:

filter => R.o(
  both(
    x => x >= filter.amount.min,
    x => x <= filter.amount.max
  ),
  prop('betrag'))

Функции, переданные both, также можно заменить:

filter => R.o(
  both(
    R.flip(R.gte)(filter.amount.min),
    R.flip(R.lte)(filter.amount.max)
  ),
  prop('betrag'))

Теперь, чтобы разобраться с filter, мы можем использовать R.converge для передачи одного и того же аргумента нескольким функциям, а затем объединять результаты каждой из них.

filter => R.o(
  R.converge(both, [
    f => R.flip(R.gte)(R.path(['amount', 'min'], f),
    f => R.flip(R.lte)(R.path(['amount', 'max'], f)
  ])(filter),
  prop('betrag'))

И теперь f находится в конечной позиции, так что его можно опустить по композиции:

filter => R.o(
  R.converge(both, [
    R.o(R.flip(R.gte), R.path(['amount', 'min'])),
    R.o(R.flip(R.lte), R.path(['amount', 'max']))
  ])(filter),
  prop('betrag'))

Доведение filter до конца становится довольно запутанным, включая переключение функций композиции.

filter => R.o(
  R.flip(R.o)(R.prop('betrag')),
  R.converge(both, [
    R.o(R.flip(R.gte), R.path(['amount', 'min'])),
    R.o(R.flip(R.lte), R.path(['amount', 'max']))
  ])
)(filter)

И наконец...

const amountPred = R.o(
  R.flip(R.o)(R.prop('betrag')),
  R.converge(both, [
    R.o(R.flip(R.gte), R.path(['amount', 'min'])),
    R.o(R.flip(R.lte), R.path(['amount', 'max']))
  ])
)

... сравнивая это с вашим оригиналом, я знаю, что предпочитаю читать:

const amountPred = filter => p =>
  p.betrag >= filter.amount.min && p.betrag <= filter.amount.max
person Scott Christopher    schedule 30.06.2018
comment
Благодарю вас! приятно видеть, что вы не всегда должны стремиться к бесточечному и что совершенно нормально оставить код без него. - person Mauravan; 02.07.2018

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

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

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

const filterPayments = pipe(
  juxt([typePred, amountPred, accountPred, currencyPred]),
  allPass,
  filter
)

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

person Scott Sauyet    schedule 30.06.2018
comment
R.juxt действительно многое убирает. Еще одна функция в Ramda, о которой я забыл :) - person Scott Christopher; 30.06.2018