是否有任何一个 (R.either) 的可变参数版本?

Is there a Variadic Version of either (R.either)?

我需要 R.either 的可变版本。在网上做了一些搜索后,我还没有找到解决方案。 R.anyPass 可以,但它 returns 是一个布尔值而不是原始值。是否已经有我忽略的解决方案?如果不是,编写可变参数效用函数的最佳方法是什么?

一个例子:

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

您可以组合使用 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. 您必须在两次 "passes" 中调用 z,即 z(...functions)(x)
  2. 虽然应该很容易添加,但是我没在意没有函数的情况"matches"
  3. 也许没什么大不了但值得注意:匹配的谓词将被执行两次

第一个函数 findTruthyFn 用于查找真实函数,或者如果其中 none 个函数 return 是真实结果,则采用最后一个函数。

第二个函数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>

这个 (non-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 因为它的 side-effects 确实看起来很脏,这可能会让我选择 customcommander 的更新版本而不是这个。

更新:

我个人更喜欢以前的版本,它更干净,但你最终可以进一步 ramdify(由于递归,你不能完全写 point-free):

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>

没有 Ramda ...

我可能会使用简单的递归来写这个 -

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 库中比较古怪的函数之一。如果您仔细阅读 documentationR.either 有两 (2) 个行为变体:它可以 return -

  1. 一个函数,它将其参数传递给两个函数中的每一个,fg,并且 return 是第一个 truthy 值 - 如果 f 的结果为真,g 不会 被评估。

  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>

加倍...

现在让我们制作一个 super-powered 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" }

你可以view the source换取R.either并与上面的veither进行比较。 Uncurried 和 restyled,你可以在这里看到它的许多相似之处 -

// 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>