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