【问题标题】:Conditional operations in function composition函数组合中的条件操作
【发布时间】:2016-08-30 10:39:04
【问题描述】:

如何根据某些逻辑条件停止或分支合成?

例如。假设我有以下代码:

compose(
  operation4
  operation3,
  operation2,
  operation1
)(myStuff);

甚至是类似的东西

myStuff
 .map(operation1)
 .map(operation2)
 .map(operation3)
 .map(operation4)

如果 myStuff 满足某些条件,我只希望执行操作 3 和 4。

我该如何实现(特别是在 JavaScript 中)?

我是否必须创建两个较小的组合并有一个单独的 if 语句,或者有没有办法在组合中包含条件?

Monads 能解决我的问题吗?如果有,怎么做?

【问题讨论】:

  • 这可能取决于您使用的框架。如果您使用自己的 compose 函数,您可能只需扩展它以允许条件(例如myStuff.map(a).if(b).map(c))。如果您使用的是外部框架,则可能需要将其拆分为两部分。
  • 如果您想在不满足条件时立即停止合成,您可以将 Either 应用为 monad(使用其 chain 方法)。如果您只想省略导致未满足条件的函数,您可以将Either 应用为应用函子(使用其ap 方法)或使用像Georg's answer中的三元函数运算符。
  • 这样做是否足够 [myStuff].filter(predicate).map(fx1).map(fx2) 或使用任何其他流库来解决这个问题?
  • @LUH3417 Either monad 选项听起来像是我正在寻找的那种东西,但我对 Monad 还不是很了解。你介意用一个实现来回答吗?
  • 嗯,我不太确定Either 是否适合这种情况。如果我理解正确,您不想检查每个函数的条件,而只想检查第三个和第四个函数。我需要更仔细地考虑一下。

标签: javascript function functional-programming function-composition


【解决方案1】:

一个简单但实​​用的方法是有一个像when(cond, f) 这样的函数,它只在cond(x) 返回true 时执行f(这又可以是一个组合):

_do = (...fns) => x => fns.reduce((r, f) => f(r), x);
_when = (cond, f) => x => cond(x) ? f(x) : x;


// example

add3 = x => x + 3;
add5 = x => x + 5;
add9 = x => x + 9;


pipe = _do(
    add3,
    add5,
    _when(x => x > 20, _do(add9))
)


console.log(pipe(30))
console.log(pipe(1))

【讨论】:

  • 将单个函数传递给do 似乎没有用。
  • 将函数命名为do 有点令人困惑,人们可能会认为它会像在 Haskell 中那样进行一元 (Kleisli) 组合(您的函数更像 appEndo . foldMap Endo)。 compose 可能是一个更好的名字。
【解决方案2】:

过早退出组合

这至少是可能的,但我认为这不是一个好主意。函数组合旨在表现得像单个函数。这是一个原子操作。因此,不提供开箱即用的过早退出。

让我们后退一步。 n 函数的组合是经典的归约:

// generic functions

const foldr = f => acc => xs =>
 xs.reduceRight((acc, x, i) => f(x) (acc, i), acc);

const comp = f => g => x => f(g(x));

const I = x => x;

const inc = x => x + 1;

// derived function

const compn = foldr(comp) (I);


// run

console.log(
  compn([inc, inc, inc, inc]) (0) // 4
);

compn 产生类似的计算:

const I = x => x;

const inc = x => x + 1;

const computation = x => inc(inc(inc(inc(I(x)))));

console.log(
  computation(0) // 4
);

如果我们想提前退出组合,我们必须同时适应迭代算法 (foldr) 和组合 (compn):

// generic functions

const foldrk = f => acc => xs => {
  const next = (i, acc) => i < 0
   ? acc
   : f(xs[i], i) (acc) (acc => next(i - 1, acc));

  return next(xs.length - 1, acc);
};

const I = x => x;

const comp = f => g => x => f(g(x));

const inc = x => x + 1;

const lt = y => x => x < y;


// derived function

compWhile = pred => foldrk(f => acc => k => k(
  x => pred(x) 
   ? comp(acc) (f) (x) 
   : x
)) (I);


// run

console.log(
  compWhile(lt(2)) ([inc, inc, inc, inc]) (0) // 2
);

只要当前返回值小于 2,组合就会继续。代码很难推理。我什至不试图解释它(这不值得努力)。无论如何,compWhile 会产生以下计算:

const comp = f => g => x => f(g(x));

const I = x => x;

const inc = x => x + 1;

const lt = y => x => x < y;

const lt2 = lt(2);

const computation = x => lt2(x) 
 ? comp(x => lt2(x)
  ? comp(x => lt2(x)
   ? comp(x => lt2(x)
    ? comp(I) (inc) (x)
    : x) (inc) (x)
   : x) (inc) (x)
  : x) (inc) (x)
 : x;


console.log(
  computation(0) // 2
);

分支组合

只需将组合分成更小的组合,它们代表所需的分支。如果你尝试编写条件函数,你将不可避免地得到不可读的代码。

如果你只有一个简单的条件并且你不想拆分你的组合,至少使分支尽可能明确,例如使用条件运算符:?

const foldr = f => acc => xs =>
 xs.reduceRight((acc, x, i) => f(x) (acc, i), acc);

const comp = f => g => x => f(g(x));

const I = x => x;

const inc = x => x + 1;

const compn = foldr(comp) (I);


console.log(
  compn([x => x >= 2 ? x : inc(x), inc, inc]) (0) // 2
);

结论

函数组合是一种原子操作。不要试图过早退出或从组合中分支。

【讨论】:

  • const compn = flip(foldr(apply)) :-)
【解决方案3】:

我可能在一定程度上误解了这个问题,但这听起来像是你可以用数组或流做的事情,例如,用反应式流或任何其他类似的库。

var myStuff = "one";
[myStuff]
  .filter(text => text.length <= 3)
  .map(text => text.split('').reverse().join(''))
  .map(text => text.toUpperCase())
  .forEach(text => console.log(text)); //prints ENO

或者使用像RxJS这样的流库。

Rx.Observable.of("one","two","three")
            .filter(text => text.length <= 3)
            .map(text => text.split('').reverse().join(''))
            .map(text => text.toUpperCase())
            .forEach(text => console.log(text)); //prints ENO, OWT

除非满足过滤条件,否则上述两个示例数据都不会流向反转和大写操作,这似乎是您要问的。

虽然这很明显,但我感觉您的问题可能与其他问题有关。无论哪种情况,我都希望这有助于讨论。

【讨论】:

  • OP 要求条件函数组合,而不是迭代算法。
  • @LUH3417 流不是迭代算法。此外,我关注的是 OP 所说的他/她想要实现的目标,而不是他/她对应该如何完成的想法。所以对我来说,这里的问题是这是否解决了问题,或者问题是否被错误地陈述或我误解了问题。
  • 流是一个异步的、延迟生成的序列,您可以在其上应用迭代算法。这与函数组合没有任何共同之处。还是我错过了什么?
  • @EdwinDalorzo 我正在寻找的是,使用您的示例,如果前两个操作的结果,我只会执行最后两个链接操作(mapforEach) (filtermap)满足了一些条件。有没有办法做到这一点而不把这条链一分为二?
  • @MarceloLazaroni 它不是已经这样做了吗?一旦将过滤器放入链中,除非满足过滤器谓词,否则不会执行其下面的其余操作。
【解决方案4】:

只需在每一步之后分析您的 someObject。带有条件回调的示例,但您可以通过不同的方式实现此检查。

function compose(...fns) {
    return function (result, conditionCallback) {
        for (var i = fns.length - 1; i > -1; i--) {
            result = fns[i].call(this, result);

            // Run here condition check if provided.
            if (conditionCallback && conditionCallback(result) === false) {
                return result;
            }
        }
        return result;
    };
};

compose(...functionList)(someObject, function(obj) {
    return obj.prop !== someValue;
});

PS:我希望理解正确,你在“构图”下是什么意思。

PPS:示例使用 ES6 特性,但降级到 ES5 非常简单。

【讨论】:

    猜你喜欢
    • 2017-07-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-02
    • 2023-03-08
    相关资源
    最近更新 更多