我对函数式编程与命令式编程有一个普遍的问题。我是一个爱好程序员,用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;
也许,这个例子没有明确定义。但我希望人们可以理解一般性问题。如果不重新计算必须重新计算,如果重新计算是非常必要的话,如何避免重复和不必要的重新计算。
答案 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
除了极少数例外,我敢肯定,每种语言都能表达出良好的代码 - 但它是一把双刃剑:每种语言无例外能够表达错误的代码