【问题标题】:Is there a Variadic Version of either (R.either)?是否有任何一个(R.either)的可变参数版本?
【发布时间】:2019-07-06 02:07:30
【问题描述】:

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

一个例子:

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

【问题讨论】:

  • 如果没有匹配项,你还不清楚你想要什么输出。但这样做可以:const varEither = (...fns) => (x, res = null, fn = fns.find(fn => res = fn(x))) => res 吗?它应该运行每个函数一次,直到它达到一个真实的结果,返回它,或者返回最后一个函数的结果。这就是你想要的吗?
  • 以上确实满足所有要求。我建议将其作为答案提交,以便考虑最佳实施。
  • 添加了它。但我个人更喜欢 customcommander 的更新答案,至少如果您已经在代码中使用 Ramda。
  • 作为一个侧面问题,哪个名称最适合接受可变参数或数组?想到的一些:everyV,varEither,everyAny,anyEither。想法?
  • 你知道他们在说什么,对吧:软件中最难的两个问题是缓存失效、命名事物和一个错误。

标签: javascript functional-programming variadic-functions ramda.js


【解决方案1】:

您可以使用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. 也许没什么大不了,但值得注意的是:匹配的谓词将被执行两次

【讨论】:

  • 感谢您的解决方案。但是,我确实需要一个最多调用每个函数一次的解决方案。此外,如果只有两个函数传入或没有一个函数返回真实响应,则上面的解决方案会给出错误:find(...) is not a function
  • @webstermath 是的,我知道在我最初的尝试中是第 2 种情况。请查看更新的答案。
  • 谢谢,您的新解决方案确实满足所有要求,我确实喜欢实施。但是,由于现在有几种解决方案可以满足可变参数的所有要求,我将等待更多的赞成票来帮助我决定接受哪一个。
  • 换能器技术的不错应用
【解决方案2】:

没有 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) 个行为变体:它可以返回 -

  1. 一个函数将其参数传递给fg 这两个函数中的每一个,并返回第一个truthy 值 - 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" }

您可以将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)

展开下面的sn-p,在自己的浏览器中验证结果-

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

我向您展示这个是因为它工作得很好并且保留了两种变体的行为,但应该避免它,因为它构建了一个更复杂的计算树。不过,展开下面的 sn-p 以验证它是否有效 -

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>

【讨论】:

  • 优秀而翔实的答案!真的觉得我学到了很多东西,不仅仅是“两者”,还有 Ramda 和一般的函数式编程。这似乎是一个很好的解决方案,并且可能是解决方案。我只是想看看其他人是否同意。此外,您的投票似乎是为“veither”这个名字投票的,不是吗?
  • @webstermath,我很乐意提供帮助。 Ramda 有许多其他可变参数函数,所以我不知道为什么 R.either 特别仅限于两个参数。这个函数特别难以命名,因为它的行为非常复杂,并且它根据输入做不同的事情。更好的设计可能会将这两个变体分开,并为每个变体命名,imo。
  • 再想一想,我根本不知道为什么会出现应用行为。将库函数用作R.lift(R.or) 的快捷方式基本上不会为您节省任何费用,但代价是巨大的复杂性。如果你有函数,使用R.either(f,g)(x)——如果你有应用函子,你可以使用R.lift(R.or)(f,g)。我在实践中没有使用 Ramda,所以不知道有多少人依赖R.either 的双重行为。根据此处的其他答案,如果其他人甚至知道应用变体,我会感到惊讶。
  • 老实说,我不记得为什么either 得到了它的行为。像or/eitherand/both 这样的函数有相当多的早期流失。老实说,我怀疑它在这种情况下是否被大量使用。但是我们在各种类型上运行的许多其他功能确实有意义。我们提供map 的实现,例如,用于数组、对象和函数,并委托给其他 Functor 的对象方法。这对我来说很有意义。 either,没那么多。在记住either 有这种行为之前,我可能会先联系lift(or)
【解决方案3】:

更新:

我个人更喜欢以前的版本,它更干净,但你最终可以将它更多地 ramdify(由于递归,你不能完全无点地写它):

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}"`);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"&gt;&lt;/script&gt;

更新:

根据@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}"`);
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"&gt;&lt;/script&gt;

你可以递归调用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
  );
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"&gt;&lt;/script&gt;

【讨论】:

  • 这很有趣。我想知道您是否可以将递归嵌套在外部R.either 的右分支中。例如R.either(R.either(...), either(...))
  • 绝对可行,它还使脚本更易于阅读和维护。好建议!
  • 感谢@Hitmands。这是一个很好的解决方案。我现在更难选择接受哪种解决方案。在我选择之前,我正在寻找赞成票和 cmets 来争论哪种解决方案最符合功能/ Ramda 原则。
  • 好吧,那么我希望你不介意我添加另一个解决方案会使你的选择更加复杂:)
【解决方案4】:

第一个函数findTruthyFn 用于查找真实函数,如果它们都没有返回真实结果,则使用最后一个函数。

第二个函数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))
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"&gt;&lt;/script&gt;

如果您想将匹配函数的调用次数限制为一个,您可以在测试之前记住这些函数:

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))
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"&gt;&lt;/script&gt;

【讨论】:

  • 感谢您的解决方案。我测试了它,它确实有效。但是,它调用匹配的谓词两次,我需要每个函数最多调用一次。
  • 我们可以记忆函数来限制调用次数为一个。查看更新。
【解决方案5】:

这个(非 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 的更新版本而不是这个。

【讨论】:

  • 但是应用函子呢? ^_^
猜你喜欢
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-10
  • 2022-01-22
  • 2020-05-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多