函数式编程与命令式编程:计算时间

时间:2017-02-28 11:57:42

标签: functional-programming imperative-programming

我对函数式编程与命令式编程有一个普遍的问题。我是一个爱好程序员,用C#实现一些工程问题。你总是在这里关注函数式编程的好处。也许我完全错了,但我不能理解为什么与精心设计的命令式程序相比,函数式编程不会浪费计算时间:

假设我有以下场景,其中两个类属性分别基于繁重计算和轻量级计算。此外,假设轻量级结果取决于繁重计算的结果,但仅需要不时。伪C#实现可能如下所示:

public class Class
{
    public double? HeavyComputationVariable { get; set; }
    public double LightComputationVariable { get; set; }

    void CalcHeavyComputation(double input)
    {
        //Some heavy time consuming computation here
        HeavyComputationVariable = resultOfHeavyComputation;
    }

    void CalcLightComputation()
    {
        if(HeavyComputationVariable == null) CalcHeavyComputation();

        //Some light computation
        LightComputationVariable = HeavyComputationVariable*resultOfLightComputation;
    }
}

所以在这个例子中,当调用轻量级计算时,只有在之前没有执行过繁重的计算。因此,轻量级计算不会导致复杂变量本身的重新计算,但仅在必要时才会重新计算。

当我理解函数式编程时,我会为复杂的计算实现一个函数,为简单的计算实现一个函数:

fHeavy (someInput) return complicated;
fSimple (fHeavy(someInput)) return simple*fHeavy;

也许,这个例子没有明确定义。但我希望人们可以理解一般性问题。如果不重新计算必须重新计算,如果重新计算是非常必要的话,如何避免重复和不必要的重新计算。

2 个答案:

答案 0 :(得分:1)

使用不可变值的好处是你甚至不必做那么多的手工工作,因为缓存(在这种情况下通常称为 memoization )是“透明的” - 你可以在不改变程序行为的情况下缓存所有内容。在某些语言中,这是由编译器完成的。

E.g。在Scala中,你可以使用“lazy vals”;这些函数在内部转换为() => A类型的函数,在第一次调用时调用,然后记忆:

lazy val heavyComputation = calcHeavyComputation()
lazy val lightComputation = heavyComputation * somethingElse

在Haskell中,这甚至是默认行为(没有特殊关键字 - 每个表达式都是懒惰和记忆,除非你使用一些魔术函数):

heavyComputation = runHeavyComputation ()
lightComputation = heavyComputation * somethingElse

在这两种情况下,这些值实际上都是以 thunks 实现的,而不是简单的“对象”。但由于没有可变性,这在指称上无关紧要。

当然,这只有在你保持纯粹功能的范围内才有效。有了副作用,它变得很困难(尽管在Scala中,如果你知道你正在做什么,你仍然可以在没有太多问题的情况下走得很远)。

答案 1 :(得分:1)

红鲱鱼

在程序中保存100 ms并不是函数式编程可以节省时间的方式。

易于理解且易于调试的高度可读,可重复使用的代码可为您节省数小时甚至数千美元。

钢头靴

  

因此,轻量级计算不会导致复杂变量本身的重新计算,但仅在必要时才会重新计算。

您可以编写错误的命令性代码,并且可以编写错误的功能代码。一种语言不会使你免于自己的无知和愚蠢。

为了提供一个具体的例子,考虑fibonacci的这两个不同的功能实现 - 确保运行它们以可视化每个人的工作量

const U = f => f(f)

const Y = U (h => f => f(x => h (h) (f) (x)))

const fib = Y (f => x => 
  (console.log('hard work', x),
    x < 2 ? x : f(x - 1) + f(x - 2)))

// tons of wasted work
console.log(fib (7)) // 13

const Ymem = (f, memo = new Map) => x =>
  memo.has(x) ? memo.get(x) : memo.set(x, f(y=> Ymem(f,memo)(y))(x)).get(x)

const fibmem = Ymem (f => x => 
  (console.log('hard work', x),
    x < 2 ? x : f(x - 1) + f(x - 2)))

// no work is duplicated
console.log(fibmem (7)) // 13

除了极少数例外,我敢肯定,每种语言都能表达出良好的代码 - 但它是一把双刃剑:每种语言无例外能够表达错误的代码