为什么图灵的某些编程语言比较完整,但缺乏其他语言的某些功能?


42

在编写(应该)挂钩到外部程序/函数的解释器时遇到一个奇怪的问题:'C'和'C ++'中的函数不能挂钩可变参数,例如,我无法创建调用'printf'的函数具有与它完全相同的参数,而必须调用采用可变参数的替代版本。这是非常有问题的,因为我希望能够制作一个持有匿名钩子的对象。

因此,我认为这很奇怪,因为ForthJavaScript和也许其他多种语言都可以很容易地做到这一点,而不必求助于汇编语言/机器代码。由于其他语言可以很容易地做到这一点,这是否意味着每种编程语言可以解决的问题类别实际上也会因语言而有所不同,即使这些语言都已经完成图灵处理


33
您应该调查图伯特。它们是一种令人着迷的编程语言,有意地尽可能少地使用一些可用的操作,同时仍要完成一些任务。它们中的大多数缺乏基本的数据类型,功能,甚至缺少诸如声明变量之类的简单内容。就我个人而言,我认为在其中进行编码非常有趣,因为它会迫使您脱离舒适区尝试不必要的困难。
DJMcMayhem

8
看一下图灵机的功能,您会发现“完成图灵”是一个非常小的障碍。基本上,在支持基本算术的同时可以“读取”,“写入”和“跳转”的所有内容都是图灵完备的。这远低于您要查看的语言功能。
aroth

2
更荒谬的是:x86处理器上的MOV指令是图灵完成的。有关详细信息,请参见cl.cam.ac.uk/~sd601/papers/mov.pdf
Gareth McCaughan

3
甚至是纸牌游戏《魔术:聚会》也完成了。图灵完备性几乎与语言的功能无关。
2016年

2
顺便说一句,还有一些非常复杂的编程语言还没有完善,例如那些校对检查器。
盛安安2016年

Answers:


68

NkN

λ

图灵完整性不会告诉我们拥有类型,内置数组/整数/字典,输入/输出功能,网络访问,多线程,动态分配,...

仅仅因为Java没有功能X(例如宏,更高级别的类型或从属类型),它就不会突然停止图灵的完善。

图灵完整性和语言表达能力是两个不同的概念。


42
Idris的设计师Edwin Brady(只有一半)开玩笑地(我不知道他是否发明过)用“俄罗斯方块完成”一词来表达“可以计算自然数上的任何可计算函数”和“可以用于编写与环境交互的重要程序”。
约尔格W¯¯米塔格

12
您可能还会提到,您可以在C / C ++中拥有这些东西。您只需要编写一些代码即可充当另一种语言在C / C ++中的编译器,而在这些语言中,您的代码将在您的C / C ++代码中编译字符串。然后,您可以在C文件中使用Java之类的程序进行编程。这将是很多工作,但是有可能(事实证明是这样,因为C / C ++已完成Turing Complete)。
Shufflepants

4
@Shufflepants我不知道说“我可以用另一种语言解释,我可以用C ++做到”是否真的有用。因此,Java,ML,C ++是等效的。具有用于系统调用(I / O)的oracle的TM也等效。出于这种原因,我担心几乎所有语言都具有同等的表现力。如果是这样,比较语言不是一个有用的概念。

9
@chi你是正确的,它们是等效的。那就是图灵完成的意义。一个Turing Complete系统可以完成的任何事情都可以在另一个Turing Complete系统中完成。在特定系统中这样做可能不方便。做各种事情的便利性是我们比较不同编程语言的主要方式。但这不是问题要问的。
Shufflepants

5
@Rhymoid如果由于内存访问原因C并非图灵完备,或者因为不能将任意信号发送到具有任意大存储空间的连接设备,那么人们可能会争辩说没有任何现实语言或设备是图灵完备。但是我不认为这是一条很有用的推理方法。
Shufflepants

47

图灵完整性是可计算性的抽象概念。如果一种语言是图灵完备的语言,那么它就可以进行任何其他图灵完备语言可以完成的计算。

但是,这并没有说明这样做有多方便。由于设计选择的原因,某些语言中容易使用的某些功能在其他语言中可能会非常困难。图灵完整性只是说您可以进行计算。举一个极端的例子,可能很难在C ++中挂钩可变函数,但是可以在C ++中编写一个可以解释可变函数的JavaScript解释器。

语言设计是一门非常有趣的艺术。必须采取的主要步骤之一是确定要构成语言主干的哪些行为。这些行为是用您的语言很容易做到的事情,因为它们内置于底层。我们决定要在每种语言中包含哪些功能。

对于您的特定示例,在开发C时,其设计目的是非常接近于当今汇编语言的运行方式。可变参数函数只是将参数推入堆栈,而几乎没有类型安全性。这些可变函数的实现留给了编译器,以确保最大的可移植性。因此,很少有关于硬件功能的假设。到JavaScript出现时,情况已经改变了。它已经在虚拟机中作为一种解释语言在运行,因此,平衡逐渐转向了便利。允许挂钩可变参数功能变得合理。即使在使用及时编译的JavaScript的情况下,我们的编译器也愿意比过去的C编译器愿意存储更多有关参数的额外信息。


1
@Wildcard我认为(实际上)所有编译器都是不正确的。大多数语言都是图灵完备的,但最终需要由汇编解释/编译为汇编,而事实并非如此。但这是一个必要的物理限制-硬件从来没有图灵强大。尽管如此,可计算性还是提供了许多有用的概念,这些概念在实际意义上确实适用于现实世界的计算机。
2016年

3
@Wildcard在这种琐碎的意义上。程序集(和C)具有固定大小的指针,该指针只能寻址有限数量的内存。从理论上讲,我们可以定义一个指针是无界自然值的程序集,但我不再称其为“程序集”,我将其称为URM或类似的名称。实际上,我们只是假装物理范围足够大以允许我们的程序运行,因此,即使计算机只是有限状态机(暗示它无法执行加法操作),我们也将其更多地视为图灵机(因此加法是可行的)。
2016年

4
@chi不太准确。首先,几乎每个人都认为C是Turing完整的,因为我们通常围绕“您有足够的内存”这一假设来分配该短语。对于很少有人担心这种假设无效的更严格的措词,C没有指定指针的大小,也没有指定可以寻址多少内存的界限。因此,即使从绝对最严格的意义上来说,“图灵完成”也确实是C。
Cort Ammon

3
@CortAmmon我不同意。如果我们将C的语义形式化,并尝试嵌入“足够的内存”假设,那么我们将失败,因为sizeof(void *)ISO C标准要求对它进行评估。这迫使我们将任何给定程序的内存量限制在一个较大的范围内,但仍然是一个限制。例如,我无法编写一个其语义添加两个任意自然数的程序。使用诸如TM磁带之类的文件(如@Hurkyl上文所述),通过I / O可以使Turing变得强大。我确实同意这实际上不是问题。
2013年

7
我建议使用新的语言C-inf,它与C完全一样,只是当通过递归或堆分配分配了过多的内存时,程序将中止,并使用sizeof(void *)和sizeof(size_t)的较大值重新编译,并从头开始重新运行。
gnasher729

26

将编程语言视为不同的陆地车辆:自行车,汽车,气垫车,火车。

图灵完整性说:“这辆车可以行驶到任何其他车可以行驶的任何地方。” 也就是说,您可以计算所有相同的函数。输入到输出,开始到结束。

但是,那句话并没有说明您如何到达那里。它可能在铁轨上,可能在道路上,或者可能在空中。同样,图灵完备性也没有说明如何计算函数。您可能使用递归,迭代或一些奇怪的细胞自动机。您可以使用或不使用类型,也可以使用动态或静态技术。但是,如果您只考虑可以计算的功能(或集合/形式语言),那么只要您是图灵完备的人,这些功能就可以为您提供相同的功能。


4
这是一个很好的类比。它也很好地扩展到了我在本网站其他地方看到的问题,即是否还有其他计算模型可以超越图灵机:在这种比喻中,飞机和宇宙飞船不仅仅是图灵完备,而快艇是另一种完全是机器类型。:)
通配符

2
比“轻快”行驶更快,可能是超级图灵计算的一个更好的类比。这可能是可能的,但大多数人认为它不是。
jmite 2016-09-28

@jmite当然,仍然没有很好的证据表明我们的大脑正在超级计算机的运转。我们(显然)无法考虑使用非图灵机可能是由于这一点,尽管这不一定是无法克服的障碍。飞机实际上是一个很好的类比-飞机只是在两点之间“直行”,而忽略了地形。如果我们可以忽略时空本身的拓扑结构,那么我们的飞行速度也将比光速还快。并不是说我是说可以忽略时空的拓扑,请注意:)
Luaan

1
@Luaan对,但是我们的大脑不一定需要超级图灵才能理解超级图灵计算机。我可以通过编写一个带有TM及其状态并将其步进到下一个状态的函数,来使用较弱的终止语言(例如,Simply Typed Lambda微积分)来描述图灵机的语义。我实际上不能用该语言运行计算机(因为它可能需要无限的步骤),但是我可以写出每一步的样子。
jmite 2016年

@Luaan,“也许仍然没有很好的证据表明我们的大脑正在使用图灵机”,但是也没有证据表明人类的思想只是图灵机。由于没有图灵机可以指向任何无法追溯到人类思想起源的思想的机器,所以仍然存在着区别,即生命可以起源思想,而机械的创造则不能。但是对于计算模型我认为图灵机确实成功地包含了任何可以合理地称为“计算”,思想和梦想的东西,尽管如此。
通配符'18

17

您实质上要问的是计算能力与通常所说的语言(或计算系统)的表达能力(或只是表达能力)之间的差异。

计算能力

计算能力指的是什么样的问题,语言可以计算。最著名的计算能力类别是等效于Universal Turing Machine的类别。还有许多其他计算系统,例如随机存取机λ演算SK组合器演算μ递归函数WHILE程序等。事实证明,所有这些都可以互相模拟,这意味着它们都具有相同的计算能力。

这产生了Church-Turing论文(以创建λ微积分的Alonzo Church和创建Universal Turing Machine的Alan Turing的名字命名)。Church-Turing-Thesis是关于可计算性的假设,有两个方面:

  1. 所有具有通用计算能力的计算系统都具有同样强大的功能,并且
  2. 遵循算法的人可以精确计算出图灵机(以及任何其他系统)可以计算的功能。

不过,第二点在思维哲学领域比计算机科学更重要。

但是,Church-Turing-Thesis 没有说两点,它们与您的问题非常相关:

  1. 各种模拟的效率如何,以及
  2. 如何方便问题的编码。

(1)的一个简单示例:在随机存取机上,复制数组所花费的时间与数组的长度成正比。但是,在Turing Machine上,它花费的时间与阵列长度的平方成正比,因为Turing Machine没有随机存储器访问权限,因此一次只能在磁带上移动一个单元。因此,它需要在数组的n 元素上移动n次才能复制它们。因此,即使在渐近情况下,我们尝试从实现细节中抽象出来,不同的计算模型也可能具有不同的性能特征。

(2)的例子比比皆是:λ演算和Python都是图灵完备的。但是,您愿意使用Python还是λ微积分编写程序?

到目前为止,我还绕过了第三个皱纹:所有这些原始系统都是由逻辑学家,哲学家或数学家设计的,而不是由计算机科学家设计的……只是因为计算机以及计算机科学都不存在。所有这些都可以追溯到1930年代初,甚至早于Konrad Zuse的第一个实验(无论如何都不是可编程的和/或Turing-complete)。他们只谈论“自然数上的可计算函数”。

事实证明,现在有很多可以表达为自然数的函数–毕竟,我们的现代计算机所获得的收益远不止于此(基本上是数字0和1的3-4个函数,仅此而已) ),但是,例如,操作系统计算什么功能?

与环境交互的I / O副作用这一概念没有被“自然数函数”的思想所抓住。但是,这是很重要的,因为正如Simon Peyton Jones曾经说过的:“所有没有副作用的纯函数都会使您的CPU变得炙手可热”,听众对此回答说:“实际上,这一面。效果也一样!”

Idris的设计师Edwin Brady(只有一半)开玩笑地(我不知道他是否发明过)用“俄罗斯方块完成”一词来表达“可以对自然数计算任何可计算函数”和“可以用于编写与环境交互的重要程序”。更具有讽刺意味的是,他通过在Idris中实现了Space Invaders克隆来证明了这一点,但是他说,他有信心Tetris可以沦为Space Invaders。

要指出的另一件事情是,不仅是图灵等价不一定足够的谈实际编写“有用”的程序,它也可能OTOH甚至不necesssary。例如SQL,只是变得图灵等效采用ANSI SQL:1999年,但它仍然是之前有用。实际上,有些人可能会争辩说,使之等效于Turing并没有增加其实用性。有许多图灵等效的领域特定语言。数据描述语言通常不是(也不应该)。总语言显然不能与图灵等效,但是您仍然可以在其中编写事件循环,Web服务器或操作系统。也有与图灵等效的语言,但实际上被认为是错误的。

因此,总的来说,图灵等效性并不是特别有趣,除非您想静态分析程序。

表现力

假设我们的计算系统具有足够强大的计算能力甚至根本无法解决我们的问题,那么接下来要做的就是以某种形式表示该系统的算法来解决该问题。换句话说:我们需要用某种计算机语言编写程序。这就是表达能力的概念。

本质上,它是指用我们特定的编程语言编写我们的程序是多么“轻松”或“令人愉快”。如您所见,该概念非常模糊,主观,而且比技术更具有心理性。

但是,尝试进行更精确的定义。最著名的(也是我所知道的最严谨的)是Matthias Felleisen在他的论文《编程语言的表现力》中(前两页进行了简短的介绍,而本文的其余部分更加生动)。

主要的直觉是:将程序从一种语言翻译为另一种语言时,您需要进行的某些更改是本地包含的(例如,将FOR循环转换为WHILE循环或将循环转换为条件GOTOs),而有些则需要更改全局变量程序的结构。

如果仅通过局部转换就可以将一种语言的一种功能替换为另一种语言的不同功能,则可以说这些功能对表达能力没有影响。这就是所谓的语法糖

另一方面,如果需要更改程序的全局结构,则可以说您要翻译的语言无法表达该功能。并且您要翻译的语言据说更具表现力(就此功能而言)。

请注意,这给出了可客观地衡量表达的定义。还要注意,该概念与上下文有关,并且是可比较的。所以,如果在语言中的每个程序一个可以被翻译成语言仅有局部的变化,并且在语言至少一个程序可以被翻译成一个仅有局部的变化,那么语言是严格比语言更有表现力一种。但是,更可能的情况是,可以同时翻译两种语言的许多程序,但是有些程序无法将两种语言翻译成另一种语言。这意味着两种语言都不比另一种语言更具表达力,它们只是具有不同的功能,可以用不同的方式表达不同的程序。

这给出了“更具表现力”的含义的正式定义,但仍未捕捉到现象背后的心理观念。例如,根据此模型,语法糖不会增加语言的表达能力,因为它只能使用局部更改来翻译。但是,我们从经验中知道,有FORWHILEIF可用的,即使他们是有条件的只是语法糖GOTO品牌表达我们的意图更加容易

事实是,不同的语言具有不同的功能,这些特征使表达对问题的不同思考方式变得更容易或更困难。有些人可能会发现一种更容易表达其意图的方式,而另一些人则可能会发现另一种方式。

我在StackOverflow上的Ruby标记中发现了一个示例:许多遵循Ruby标记的用户声称,循环比递归更容易理解,而递归仅适用于高级功能程序员,而循环对于新手则更直观,但是我看到了多种情况完整的新手,他们会直观地编写如下代码:

def rock_paper_scissors
  get_user_input
  determine_outcome
  print_winner
  rock_paper_scissors # start from the top
end

这通常导致几个人评论“这不起作用”和“他们做错了”,而“正确方法”是这样的:

def rock_paper_scissors
  loop do
    get_user_input
    determine_outcome
    print_winner
  end
end

因此,显然,对于某些人来说,尾循环是一种比循环构造更自然的表达“循环”概念的方式。

摘要

两种语言与图灵等效的事实说明了一件事,那就是一件事:它们可以像图灵机一样在自然数上计算相同的一组函数。而已。

它没有说出它们计算这些函数的速度。它没有说明易于表达这些功能的任何内容。除了计算自然数上的功能(例如,链接到C库,从用户读取输入,将输出写入屏幕外)外,它什么也没说。

这是否意味着即使每种语言都已完成,每种编程语言可以准确解决的问题类别也会因语言而异?

是。

  1. 术语“ Turing-complete”(仅涉及自然数的计算功能)没有涵盖这些问题,例如在屏幕上打印。两种语言可以是图灵完备的,但是一种可以允许在屏幕上打印,而另一种则不能。
  2. 即使两种语言都可以解决相同的问题,也无法说明编码的复杂程度以及表达这种编码的难易程度。例如,C可以解决Haskell可以解决的所有问题,只需用C编写Haskell解释器即可。但是您必须首先编写Haskell解释器才能以这种方式解决问题!

7

所有图灵完整的编程语言都可以实现相同的算法集。因此,如果您发现某些算法很难以特定的语言实现,则并不意味着它是不可能的。

请记住,一种语言由语法和语义组成。有时,属于某种语言的一组单词并不是图灵完整的最低要求,有些功能使事情变得容易(这就是为什么将它们称为features)的原因。如果除去这些功能,该语言仍然是图灵完整的。

其中一些可能令人感兴趣:


5

所有图灵完备的语言都可以计算相同的东西。

如果您尝试实现现代语言,则会注意到它的大多数功能都没有添加任何计算功能。这些功能中的许多功能都可以简化为同一语言中已经存在的简单功能。

这里有些例子:

  • 如果没有枚举,则可以使用整数。
  • 如果没有知道缓冲区大小的缓冲区,则可以创建一个包含大小和不知道其大小的缓冲区的类型。
  • 如果您没有缓冲区的边界检查,则只需在每次使用它们时检查索引。
  • 如果没有可变参数函数,则可以创建一个函数,该函数采用一个知道其大小的缓冲区,并从该缓冲区中读取与您从形式参数中获得的信息相同的信息。
  • 如果没有运算符,则可以使用函数。
  • 如果您没有可以相互继承的类型,则可以创建包含彼此的类型,并通过额外的间接访问级别访问它们,而子类型化只是将句柄类型转换为整个类型处理到包含的类型。
  • 如果没有委托,但有函数指针,则可以创建一个包含引用对象和函数指针的类型。
  • 如果没有委托,但是有接口,则可以声明一个接口,该接口包含具有所需签名的单个方法。
  • 如果没有通用类型,则可以使用仅假定您感兴趣的上限或下限的非通用类型(并可能在使用位置执行适当的强制转换,以使编译器满意)。
  • 如果您没有线性/仿射类型系统,则可以避免多次使用任何变量。
  • ...等等。

主流语言设计的重点是使我们更轻松更方便地更快地计算事物,及早发现错误,针对未知组件进行编程,使并行性更安全等功能。

纯粹是用于计算的东西很久以前就被确定下来。


4

现有的答案正确地指出,图灵完整性不是比较语言的好方法。实际上,几乎所有语言都已经完成了图灵。(《超人总动员》曾经说过:“如果每个人都很特别,那么没人会特别。”)

但是,它可能的语言表达能力与数学精度比较。看费莱森的“程序语言的表达力”。大概地,这个想法是要提出以下问题:是否可以仅通过局部更改将A语言的程序转换为B语言的程序?换句话说,费莱森为您的直觉提供了数学上精确的形式。


2

在其他所有人的答案之上,这是另一个比喻。

洗衣服需要三件事:一些装有水的容器,某种洗涤剂和搅拌机制。这可以通过许多方式实现。储水器是容纳足够水的任何东西(例如,浴缸,湖泊,河流)。搅动机构可以是机械设备,搓衣板,甚至是用来殴打衣服的岩石。洗涤剂也有各种各样的形式。

那么,现代化的电脑洗衣机和河边的石头之间有什么区别?

它归结为三件事:效率,安全性和便利性。某些洗涤方法消耗的能源更少,对环境的污染更少,使用的水更少,等等。某些洗涤方法需要较少的重复手动操作(这会导致受伤)或在恶劣天气下不在家。而且某些洗涤方法不需要人工来照做。

图灵完备的编程语言是通用的,因此要承担多个任务。但是,对于给定的任务,某些编程语言比其他编程语言更有效,更方便和更安全(从某种意义上说,在实际使用该程序时可能出错的地方更少)。


2

其他人提供了很多好的答案,但是他们没有明确提到曾经使我感到困惑的警告:图灵完整性并不意味着一种语言可以从输入到输出表达任意可计算的功能。它比较弱:必须有某种方式将可计算函数集的域和范围表示为输入和输出,以便这些函数中的每个映射到一个程序,该程序将其输入表示为相应的输出。

以表达图灵机的语言为例。该语言中的每个程序都是图灵机。

现在考虑所有仅读取和写入字符a,b和空格的Turing机器的子语言。它是Turing完整的,但是它不能表达任何在所有输入上产生c的程序,因为它不能编写任何cs。它只能在编码为as和bs字符串的输入和输出上表达所有可计算函数。

因此,并非所有图灵完备的语言都可以计算相同的东西,即使我们将这些东西限制为从其潜在输入到潜在输出的可计算函数时,也并非如此。该语言可能要求输入和输出以某些方式进行编码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.