Как составить карри функции в стиле без точек в Ramda? - PullRequest
0 голосов
/ 09 апреля 2020

Моя команда переезжает из Лоды sh в Рамду и входит в более глубокие части стиля функционального программирования. Мы экспериментировали больше с compose, et c и натолкнулись на этот шаблон:

const myFunc = state => obj => id => R.compose(
  R.isNil,
  getOtherStuff(obj),
  getStuff(state)(obj)
)(id)

(Конечно, мы можем опустить детали => id и (id). Добавлено для ясность.)

Другими словами, у нас есть много функций в нашем приложении (это React + Redux для некоторого контекста), где нам нужно составлять функции, которые принимают аналогичные аргументы или где последняя функция должна получить все свои аргументы перед передачей следующей функции в строке compose. В приведенном мной примере это будет id, затем obj, затем state для getStuff.

Если бы не было функции getOtherStuff, мы могли бы R.curry the myFunc.

Есть ли элегантное решение, которое было бы бессмысленным? Это кажется достаточно распространенным паттерном в FP.

Ответы [ 2 ]

3 голосов
/ 10 апреля 2020

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

const myFunc2 = o (o (o (isNil)), o (liftN (2, o) (getOtherStuff), getStuff)) 

Примечание , что o - это просто (Рамда-карри) бинарная версия обычного вариади Рамды c compose функция.

Я действительно не понял этого. Я обманул. Если вы можете прочитать Haskell код и написать несколько базовых c вещей с его помощью, вы можете использовать замечательный сайт Pointfree.io для преобразования указанного кода в бессмысленный.

I ввел Haskell версию вашей функции:

\state -> \obj -> \id -> isNil (getOtherStuff obj (getStuff state obj id))

и получил обратно:

((isNil .) .) . liftM2 (.) getOtherStuff . getStuff

, которую я, с небольшим недоумением, смог преобразовать в версию выше. Я знал, что мне придется использовать o вместо compose, но потребовалось немного времени, чтобы понять, что мне придется использовать liftN (2, o) вместо lift (o). Я до сих пор не пытался выяснить, почему, но Haskell действительно не понял бы магическое карри c Рамды, и я предполагаю, что это связано с этим.

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

const isNil = (x) => 
  `isNil (${x})`

const getStuff = (state) => (obj) => (id) =>
  `getStuff (${state}) (${obj}) (${id})`

const getOtherStuff = (obj) => (x) =>
  `getOtherStuff (${obj}) (${x})`

const myFunc = state => obj => id => R.compose(
  isNil,
  getOtherStuff (obj),
  getStuff (state) (obj)
)(id)


const myFunc2 = o (o (o (isNil)), o (liftN (2, o) (getOtherStuff), getStuff)) 


console .log ('Original   : ', myFunc ('state') ('obj') ('id'))
console .log ('Point-free : ', myFunc2 ('state') ('obj') ('id'))
.as-console-wrapper {min-height: 100% !important; top: 0}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {o, liftN} = R                                      </script>

Не стоит

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

Бессмысленное использование может привести к некоторому элегантному коду. Но стоит использовать только тогда, когда это так; когда это скрывает ваши намерения, пропустите это.

1 голос
/ 09 апреля 2020

Я не знаю, почему вы не можете карри, хотя:

const myFunc = curry(state, obj) => R.compose(
  R.isNil,
  getOtherStuff(obj),
  getStuff(state)(obj)
));

или

const myFunc = curry(state, obj, id) => R.compose(
  R.isNil,
  getOtherStuff(obj),
  getStuff(state)(obj)
)(id));

Я не уверен, что вижу здесь бессмысленное решение (в его нынешнем виде) ). Есть некоторые менее интуитивные комбинаторы, которые могут применяться. Другая вещь, которую я хотел бы рассмотреть, заключается в том, имеют ли функции getStuff и getOtherStuff свои подписи в правильном порядке. Может быть, было бы лучше, если бы они были определены в следующем порядке: obj, state, id.

Проблема в том, что объект нужен в двух разных функциях. Возможно, перезапустите getStuff, чтобы вернуть пару, и getOtherStuff, чтобы взять пару.

const myFunc = R.compose(
  R.isNil,         // val2 -> boolean
  snd,             // (obj, val2) -> val2
  getOtherStuff,   // (obj, val) -> (obj, val2)
  getStuff         // (obj, state, id) -> (obj, val)
);

myFunc(obj)(state)(id)

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

getStuff = curry((obj, state, id) => {
   const val = null;
   return R.pair(obj, val);
}

getOtherStuff = curry((myPair) => {
   const obj = fst(myPair)
   const val2 = null;
   return R.pair(obj, val2);
}

fst = ([f, _]) => f
snd = ([_, s]) => s

==== =

Обновление по вопросу о комбинаторах. Из http://www.angelfire.com/tx4/cus/combinator/birds.html есть комбинатор скворец (S):

λa.λb.λc.(ac)(bc)

, записанный более es6

const S = a => b => c => a(c, b(c))

или функция, которая принимает три параметра а, б, c. Мы применяем c к выходу из новой функции, а c к b, оставляя все, что немедленно применяется к функции, повторяющейся с c, применяемой к.

в вашем примере мы могли бы написать это как

S(getOtherStuff, getStuff, obj)

, но это может не сработать сейчас, когда я смотрю на это. потому что getStuff не полностью удовлетворен до того, как его применили к getOtherStuff ... Вы можете начать собирать вместе решение головоломки, что иногда забавно, но не то, что вам нужно в вашем рабочем коде. Есть книга https://en.wikipedia.org/wiki/To_Mock_a_Mockingbird людям нравится, хотя это сложно для меня.

Мой самый большой совет - начинайте думать обо всех функциях как одинарные.

...