纯函数式语言真的能保证不变性吗?

时间:2011-04-09 19:03:59

标签: functional-programming immutability purely-functional

在纯粹的功能性语言中,还不能定义一个"赋值"运算符,比如说"< - ",这样命令,比如说" i< -3"而不是直接分配不可变变量i,会创建一个副本整个当前的调用堆栈,除了在新的调用堆栈中用3替换i,并从那一点开始执行新的调用堆栈?鉴于没有实际改变的数据,仍然不会被认为是纯粹的功能"根据定义?当然编译器只是简单地将优化分配给i,在这种情况下,命令式和纯函数之间的区别是什么?

3 个答案:

答案 0 :(得分:5)

纯函数式语言(如Haskell)具有对命令式语言进行建模的方法,并且他们也不会羞于承认它。 :)

http://www.haskell.org/tutorial/io.html,特别是7.5:

  

所以,最后,简单地说是Haskell   重新发明了势在必行的轮子?

     

从某种意义上说,是的。 I / O monad   构成一个小小的必要条件   Haskell内部的子语言,因此   程序的I / O组件可以   看似与普通的命令相似   码。但有一个重要的   差异:没有特别之处   用户需要处理的语义   用。特别是,等于   哈斯克尔的推理不是   损害。迫切的感觉   程序中的monadic代码没有   有损于功能方面的   哈斯克尔。经验丰富的功能   程序员应该能够最小化   的必要组成部分   程序,只使用I / O monad   最低限度的顶级   测序。 monad干净利落   分离功能和   命令式程序组件。在   对比,命令式语言   功能子集通常不会   两者之间有任何明确的障碍   纯粹的功能和命令   世界。

因此,函数式语言的价值不在于它们使状态变异不可能,而是它们提供了一种方法,允许您将程序的纯函数部分与状态变异部分分开。

当然,你可以忽略这一点并以命令式的方式编写你的整个程序,但是你不会利用该语言的设施,为什么要使用它呢?

<强>更新

你的想法没有你想象的那样有缺陷。首先,如果只熟悉命令式语言的人想要遍历一系列整数,他们可能想知道如何在没有增加计数器的情况下实现这一目标。

但是当然你只是编写一个充当循环体的函数,然后让它自己调用。函数的每次调用对应于“迭代步骤”。并且在每次调用的范围内,参数具有不同的值,就像递增变量一样。最后,运行时可以注意到递归调用出现在调用的最后,因此它可以重用函数调用堆栈的顶部而不是增长它(tail call)。即使这个简单的模式几乎具有你想法的所有风格 - 包括编译器/运行时悄悄地插入并实际发生突变(覆盖堆栈的顶部)。它不仅在逻辑上等同于具有变异计数器的循环,而且实际上它使CPU和内存在物理上做同样的事情。

您提到的GetStack会将当前堆栈作为数据结构返回。这确实会违反功能纯度,因为每次调用时它都必须返回不同的东西(没有参数)。但是函数CallWithStack如何传递给你自己的函数,它会回调你的函数并将当前堆栈作为参数传递给它?那将是完全可以的。 CallCC有点像这样。

答案 1 :(得分:4)

Haskell并不容易给你内省或“执行”调用堆栈的方法,所以我不会太担心那个特殊的怪异方案。但是一般情况下, 为真,可以使用不安全的“函数”(例如unsafePerformIO :: IO a -> a)来破坏类型系统。这个想法是让违反纯度变得困难,而不是不可能。

实际上,在许多情况下,例如在为C库创建Haskell绑定时,这些机制是非常必要的......通过使用它们,您可以从编译器中消除纯度证明的负担并自行处理。< / p>

有人建议通过禁止类型系统的这种颠覆来实际保证安全;我对它不太熟悉,但你可以阅读它here

答案 2 :(得分:2)

不变性是语言的属性,而不是实现。

如果引用位置a <- expr的值似乎已从程序员的角度更改,则复制数据的操作a仍然是必要的操作。

同样,纯函数式语言实现可以覆盖并重用变量到其内容,只要每个修改对程序员是不可见的。例如,只要语言实现可以推断出在任何地方都不需要旧列表,map函数原则上可以覆盖列表而不是创建新列表。