Есть ли вариант Variadic (R.either)? - PullRequest
5 голосов
/ 01 июля 2019

Мне нужна вариационная версия R.either. После некоторых поисков в Интернете я не нашел решения. R.anyPass будет работать, но он возвращает логическое значение вместо исходного значения. Есть ли уже решение, которое я упустил? Если нет, то каков был бы самый оптимальный способ написать переменную или функцию полезности?

Пример:

const test = variadicEither(R.multiply(0), R.add(-1), R.add(1), R.add(2))
test(1) // => 2 

Ответы [ 5 ]

4 голосов
/ 01 июля 2019

Вы можете использовать комбинацию reduce + reduced:

const z = (...fns) => x => reduce((res, fn) => res ? reduced(res) : fn(x), false, fns);

console.log(
  z(always(0), always(10), always(2))(11),
  z(always(0), always(''), always(15), always(2))(11),
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {reduce, reduced, always} = R;</script>

(предыдущая попытка)

Я бы сделал что-то вроде этого:

const z = unapply(curry((fns, x) => find(applyTo(x), fns)(x)));

console.log(

  z(always(0), always(15), always(2))(10),
  z(always(0), always(''), always(NaN), always(30), always(2))(10),

);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {unapply, curry, find, applyTo, always} = R;</script>

Однако есть три основных предостережения!

  1. Вы должны позвонить z в два "прохода", т.е.z(...functions)(x)
  2. Хотя это должно быть легко добавить, меня не волновал случай, когда ни одна функция не «совпадает»
  3. Возможно, это не имеет большого значения, но стоит отметить: соответствующий предикат будетисполняться дважды
3 голосов
/ 04 июля 2019

Без Рамды ...

Я бы написал это, используя простую рекурсию -

const always = x =>
  _ => x

const identity = x =>
  x

const veither = (f = identity, ...more) => (...args) =>
  more.length === 0
    ? f (...args)
    : f (...args) || veither (...more) (...args)

const test =
  veither
    ( always (0)
    , always (false)
    , always ("")
    , always (1)
    , always (true)
    )

console .log (test ())
// 1

Но это еще не все ...

R.either должна быть одной из самых эксцентричных функций в библиотеке Ramda. Если вы внимательно прочитаете документацию , R.either имеет два (2) варианта поведения: он может вернуть -

  1. функция, которая передает свой аргумент каждой из двух функций f и g и возвращает первое истинное значение - g будет не оценивать, если результат f правдив.

  2. Или аппликативный функтор

В подписи R.either написано -

either : (*… → Boolean) → (*… → Boolean) → (*… → Boolean)

Но это определенно немного обманывает. Для наших двух вышеописанных случаев следующие две подписи гораздо ближе -

// variant 1
either : (*… → a) → (*… → b) → (*… → a|b)

// variant 2
either : Apply f => f a → f b → f (a|b)

Давайте подтвердим эти два варианта простыми тестами -

const { always, either } =
  R

const { Just, Nothing } =
  folktale.maybe

// variant 1 returns a function
const test =
  either
    ( always (0)
    , always (true)
    )

console.log(test()) // => true

// variant 2 returns an applicative functor
const result =
  either
    ( Just (false)
    , Just (1)
    )

console.log(result) // => Just { 1 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>

Удвоить ...

Теперь давайте сделаем супер-мощный veither, который предлагает ту же двойную возможность, что и R.either -

const vor = (a, ...more) =>
  a || vor (...more)

const veither = (f, ...more) =>
  f instanceof Function
    // variant 1
    ? (...args) =>
        f (...args) || veither (...more) (...args)
    // variant 2
    : liftN (more.length + 1, vor) (f, ...more)

Он работает так же, как R.either, за исключением того, что теперь он принимает два или более аргумента. Поведение каждого варианта поддерживается -

// variant 1 returns a function
const test =
  veither
    ( always (false)
    , always (0)
    , always ("fn")
    , always (2)
    )

test () // => "fn"

// variant 2 returns an applicative functor
veither
  ( Just (0)
  , Just (false)
  , Just ("")
  , Just ("ap")
  , Just (2)
  )
  // => Just { "ap" }

Вы можете просмотреть источник для R.either и сравнить его с veither выше. Неторопливый и рестайлинговый, вы можете увидеть его много общего здесь -

// either : (*… → a) → (*… → b) → (*… → a|b)

// either : Apply f => f a -> f b -> f (a|b)

const either = (f, g) =>
  isFunction (f)
    // variant 1
    ? (...args) =>
        f (...args) || g (...args)
    // variant 2
    : lift (or) (f, g)

Разверните фрагмент ниже, чтобы проверить результаты в вашем собственном браузере -

const { always, either, liftN } =
  R

const { Just, Nothing } =
  folktale.maybe

const vor = (a, ...more) =>
  a || vor (...more)

const veither = (f, ...more) =>
  f instanceof Function
    ? (...args) =>
        f (...args) || veither (...more) (...args)
    : liftN (more.length + 1, vor) (f, ...more)

// variant 1 returns a function
const test =
  veither
    ( always (false)
    , always (0)
    , always ("fn")
    , always (2)
    )

console .log (test ()) // "fn"

// variant 2 returns an applicative functor
const result =
  veither
    ( Just (0)
    , Just (false)
    , Just ("")
    , Just ("ap")
    , Just (2)
    )

console .log (result) // Just { "ap" }
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>

задним числом и предвидением ...

С помощью одной маленькой уловки мы можем пропустить всю церемонию рассуждений о наших собственных veither. В этой реализации мы просто совершаем повторяющийся вызов R.either -

const veither = (f, ...more) =>
  more.length === 0
    ? R.either (f, f) // ^_^
    : R.either (f, veither (...more))

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

const { always, either } =
  R

const { Just, Nothing } =
  folktale.maybe

const veither = (f, ...more) =>
  more.length === 0
    ? either (f, f)
    : either (f, veither (...more))

// variant 1 returns a function
const test =
  veither
    ( always (false)
    , always (0)
    , always ("fn")
    , always (2)
    )

console .log (test ()) // "fn"

// variant 2 returns an applicative functor
const result =
  veither
    ( Just (0)
    , Just (false)
    , Just ("")
    , Just ("ap")
    , Just (2)
    )

console .log (result) // Just { "ap" }
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/folktale/2.0.1/folktale.min.js"></script>
2 голосов
/ 03 июля 2019

Обновление:

Лично я предпочитаю предыдущую версию, она намного чище, но в конечном итоге вы можете еще больше ее расширить (вы не можете написать ее совершенно бессмысленно из-за рекурсии):

const either = (...fns) => R.converge(R.either, [
  R.head,
  R.pipe(
    R.tail,
    R.ifElse(R.isEmpty, R.identity, R.apply(either)),
  ),
])(fns);

const result = either(R.always(null), R.always(0), R.always('truthy'))();

console.log(`result is "${result}"`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Обновление:

Согласно предложению @ customcommander, рекурсия может быть вложена в нужную ветвь, чтобы иметь более чистый сценарий ...

const either = (...fns) => (...values) => {
  const [left = R.identity, ...rest] = fns;
  
  return R.either(
    left, 
    rest.length ? either(...rest) : R.identity,
  )(...values);
}

const result = either(R.always(null), R.always(0), R.always('truthy'))();

console.log(`result is "${result}"`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

Возможно, вы могли бы вызвать R.either рекурсивно ...

const either = (...fns) => (...values) => {
  const [left = R.identity, right = R.identity, ...rest] = fns;
  
  return R.either(left, right)(...values) || (
    rest.length ? either(...rest)(...values) : null
  );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
1 голос
/ 02 июля 2019

Эта (не Ramda) версия довольно проста и, кажется, делает то, что нужно:

const varEither = (...fns) => (x, res = null, fn = fns.find(fn => res = fn(x))) => res

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

const varEither = (...fns) => (...xs) => {
  let res = null;
  fns .find (fn => res = fn (...xs) )
  return res;
}

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

1 голос
/ 01 июля 2019

1-я функция findTruthyFn используется, чтобы найти функцию truty или взять последнюю функцию, если ни одна из них не возвращает достоверный результат.

2-я функция fn получает список функций и значение, использует findTruthyFn, чтобы найти функцию, и применяет ее к значению, чтобы получить результат.

const { either, pipe, applyTo, flip, find, always, last, converge, call, identity } = R

const findTruthyFn = fns => either(
  pipe(applyTo, flip(find)(fns)), 
  always(last(fns))
)

const fn = fns => converge(call, [findTruthyFn(fns), identity])

const check = fn([x => x + 1, x => x - 1])

console.log(check(1))
console.log(check(-1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

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

const { either, pipe, applyTo, flip, find, always, last, converge, call, identity, map, memoizeWith } = R

const findTruthyFn = fns => either(
  pipe(applyTo, flip(find)(map(memoizeWith(identity), fns))), 
  always(last(fns))
)

const fn = fns => converge(call, [findTruthyFn(fns), identity])

const check = fn([
  x => console.log('1st called') || x + 1, 
  x => console.log('2nd called') || x - 1
])

console.log(check(1))
console.log(check(-1))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
...