函数式编程中的纯函数是没有副作用的函数。其中一个含义是它不能改变输入参数的值。这可以被视为内存利用的缺点吗?
例如假设我有一个函数,只需要一个列表并在其中添加另一个元素。在C ++中,它可以像
void addElem(std::vector& vec, int a)
{
vec.insert(a);
}
这个函数显然没有使用传递对象已经占用的大量内存
但在Haskell中也会出现类似的情况。
addElem :: [Int] -> Int -> [Int] addElem xs a = xs ++ [a]
现在在这个计算中,因为xs没有改变它的值。所以我说的是,在某些时候,该函数将消耗xs的内存双倍大小,一个用于xs,另一个用于返回值。或者以某种方式延迟调用确保实际xs只通过在末尾添加一个元素才能返回?
我知道Monad是一种可以产生副作用的方法。但Monad可以用来简单地修改输入并返回它的值吗?
也可以将xs ++ [a]更改为:xs消耗更少的内存?
答案 0 :(得分:10)
纯度意味着该函数无法进行破坏性更新,因此如果
xs ++ [a]
已完全评估,必须提供xs
的副本。如果xs
延迟生成并且未在其他任何地方引用,则可能在常量空间中发生这种情况,因此可以在创建时对其进行垃圾回收。或者它可能需要除xs
之外的副本 - 但纯度允许两个列表的单元格指向相同的数据,因此不需要复制数据,只需要复制脊椎(由最终缺点修改)。但是,如果制作副本,旧值xs
仍可用。
也可以将xs ++ [a]更改为:xs消耗更少的内存?
是的,可以简单地重用xs
,只需创建一个新的列表单元格并让其尾指针指向xs
。
来自评论:
我不希望C ++函数是纯粹的。我希望它改变输入的状态。这就是我想成为问题的全部要点。
在这种情况下,您需要一个不纯的功能/动作。对于某些数据结构来说,这是可能的,但对于列表,必须重新复制/构造单元。但是向std::vector<T>
添加元素可能还需要重新分配。
答案 1 :(得分:8)
是的,纯FP可能比命令式编程更耗费内存,但这取决于您编写程序的方式以及编译器的智能程度。特别是Haskell编译器具有非常强大的优化器,并且可能能够将纯FP代码转换为重用已分配内存的高效机器代码。这需要编写好的FP代码,因为即使是最聪明的编译器也不会包含优化来处理仅使用表面上类似的FP结构来模拟命令式编程的程序。
请注意,您的C ++示例无效。如果你的意思是
v[0] = a; // assuming v.size() > 0
然后不进行任何分配。如果你的意思是
v.append(a);
那么可能会也可能不会分配,具体取决于v
的容量。
或者以某种方式懒惰调用确保实际上只通过在末尾添加一个元素来返回xs?
取决于在其上下文中实现和使用表达式。完全评估xs ++ [a]
后,它会复制整个列表xs
。如果对其进行部分评估,则可以在none和len(xs)
之间进行任意数量的分配。
还可以将xs ++ [a]更改为:xs消耗更少的内存吗?
是的,它将其从O(n)最坏情况改为O(1)最坏情况分配/额外内存使用。同样适用于时间复杂性。在Haskell中处理列表时,永远不要追加到最后。如果这就是您所需要的,请使用difference list。
答案 2 :(得分:4)
我认为你的两个例子之间的相关差异是数据结构的选择,而不是纯粹与不纯的问题。
您可以在两种方法中使用单链接列表,并且可以在两者中使用具有恒定时间更新的数组。在纯粹的情况下,对数据结构的更新可能在语义上制作副本,但这并不一定意味着它物理。
例如,Ropes是不可变数组的变体,允许持续时间连接。您还可以通过在内部使用写时复制方案来实现具有恒定时间功能更新的不可变数组抽象(其中a.set(i,x)在语义上返回副本),如果您实际访问该方案,则仅执行物理副本创建新版本后的旧版本(即如果您使用纯数据结构的持久性功能,这在不纯的情况下是不可用的)。答案 3 :(得分:3)
ab·strac·tion [ab-strak-shuh n]
除了具体的现实,特定的对象或实际情况之外,将某事视为一般品质或特征的行为。
所有这些因素无法在同一时间达到平衡
所有非平凡的抽象在某种程度上都是漏洞。
函数式编程可以看作是一种抽象,并且由于所有抽象都会泄漏,因此需要进行一些权衡。
答案 4 :(得分:3)
现在在这个计算中,因为xs没有改变它的值。所以我说的是,在某些时候,该函数将消耗xs的内存双倍大小,一个用于xs,另一个用于返回值。
不一定。拥有不可变数据的优点是可以共享。因此编译器可以通过在这种情况下共享x来进行优化。所以大小将与c ++一样保持相同。
或者以某种方式懒惰调用确保实际xs只是通过在末尾添加一个元素才能返回?
懒惰只是意味着在实际需要值时进行评估,它不能保证较少的内存使用量。另一方面,由于懒惰而创建的thunk可能会使用更多内存。
我知道Monad是一种有副作用的方法。但Monad可以用来简单地修改输入并返回它的值吗?
你是部分正确的。 Monad用于组成具有副作用的代码。您可以很好地使用可变向量并编写与IO monad中的c ++示例非常相似的代码。
还可以将xs ++ [a]更改为:xs消耗更少的内存吗?
这是依赖于编译器的,但我认为它会复制整个列表并在最后添加元素。 (我不是这方面的专家)。
您可以随时分析代码并检查内存消耗,这是了解此问题的最佳方式。
答案 5 :(得分:1)
不同的语言有不同的共同习语,而且 实施者努力使这些习语尽可能有效。 我会不假设_因为某些东西需要额外的东西 在C ++中的开销,在另一种语言中也是如此, 就像我不认为最有效的成语一样 另一种语言在C ++中最有效。
话虽如此:我目前正在努力做大,高
我们经常返回std::vector
的性能应用程序
我们没有发现这是一个问题。像NRVO和搬家这样的事情
语义最终意味着这在C ++中非常有效
同样。