当我编码时,我总是写一些小块的单元,并经常编译它。这有助于我确保一切正常运行,但这非常耗时。是否有任何编程语言可以支持我们同时进行编码和运行?我的意思是只要按键导致有效代码,编辑的效果就会被合并到正在执行的程序中。
7 回答
一旦按键产生有效代码,编辑的效果就被纳入执行程序?
如上所述,许多语言都提供交互式读取/编辑/评估/打印循环,但您要求更多。1980 年代的Symbolics Lisp 机器提供的功能几乎与您描述的一样;您确实必须按一个键来更新正在运行的系统。您甚至可以在不干扰调用堆栈上先前版本的旧激活的情况下替换一个函数。
1970 年代和 1980 年代是交互式开发环境的黄金时代,从那时起,该领域就萎靡不振。如今,人们认为 Visual Studio 是一个高度交互的编程环境——它确实如此,但不那么动态。
我认为你今天最接近复制这种动态体验的是Squeak Smalltalk,它有一个非常复杂的 IDE,它非常基于 Xerox PARC 的 Smalltalk-80 环境构建(并且由同一个人)。
LISP、Scheme、Haskell、Perl、Python、Ruby、Maple、Mathematica、MATLAB 等。大多数解释型语言都可以做到这一点。一旦您完成输入,您的代码就会被解释并运行。
哎呀,即使是命令外壳也很重要。
引用维基百科:命令行解释器:
尽管大多数用户认为 shell 是一种交互式命令解释器,但它实际上是一种编程语言,其中每个语句都运行一个命令。因为它必须同时满足命令执行的交互和编程方面,所以它是一种奇怪的语言,既受历史影响,也受设计影响。
——布赖恩·克尼汉和罗伯·派克
如需视频演示,请观看Conway 在 APL 中的生命游戏。
您正在寻找的东西有时被称为活跃系统、增量系统或自我维持系统 (S³)。
它实际上不是编程语言的属性,而是开发环境的属性。它与编程语言唯一有关的是,一些编程语言社区强烈反对活泼的想法,因此这些社区往往不会产生活泼的工具,而其他社区则无法想象没有活泼系统的生活,所以他们倾向于生产它们。
例如,几乎所有的 Smalltalk 环境和许多 Lisp 环境都是活跃的,而我知道没有一个活跃的 C 或 C++ 环境。曾经有一个活跃的 Java 环境,称为 IBM Visual Age for Java(它实际上是由 IBM 的 Smalltalk 部门用 Smalltalk 编写的,基于 IBM Visual for Smalltalk,所以这并不奇怪),但是当它用 Java 重写时作为 Java 微型版的 Visual Age(您可能更了解它的当前名称 Eclipse),它失去了活力。
您询问“同时编码和运行”。好吧,在一个活泼的系统中,它们甚至不是“并列”的,它们实际上是一回事:没有编码和运行的区别,没有IDE和应用程序的区别,没有编译的区别时间和运行时间。
您经常在 Smalltalk 中开发软件的方式是,您只需写下您想要的并运行它:
aCalculator ← Calculator new.
aCalculator compute: '1 + 1'.
(顺便说一句:“运行它”只是意味着将一小段代码写入屏幕上任何位置的任何文本区域,突出显示它并单击“运行”。)
[注:左箭头是赋值(想想=
),向上箭头是return
。最初的 Smalltalk 系统将它们放在键盘上,现代系统通常使用:=
and^
代替。;
此外,空格是消息发送(Java 中的“点”),句点在大多数语言中终止句子( )。方法的参数在冒号后的消息本身中内联传递,而不是在括号中。]
当然,这不起作用,因为Calculator
该类还不存在。因此,调试器弹出(我有没有提到运行和调试之间没有区别?)它为您提供的选项之一是创建该类。您无需重新启动程序,它只会继续运行。现在你得到一个新的错误,因为该compute:
方法不存在。同样,调试器提供为您创建方法,您可以直接在此处输入代码:(我是否提到调试器和编辑器之间没有区别?)
compute: aString
↑ 2.
系统已经为您填写好方法名称,并为方法参数选择了一个(稍微)合理的名称。您只需键入第二行。
(顺便说一句:如果这个工作流听起来你很熟悉,那应该是。Kent Beck 基于他的两个测试框架(SUnit 和 JUnit)的对象模型和执行模型,以及测试驱动开发和红绿-在这个工作流上重构循环。)
请注意,在此过程中,我们没有停止或重新启动正在运行的程序。当系统运行时,我们总是在系统内部编辑实时系统的源代码。事实上,您实际上无法停止 Smalltalk 程序或 Smalltalk 系统!即使您关闭了 Smalltalk 系统,它实际上所做的是将系统的整个状态(每个类、每个对象、每个变量、每个线程、每个窗口,甚至鼠标指针的位置)序列化到磁盘以及当您重新启动它,你就在你离开的地方。系统从未停止过,它只是被冻结了。(如果您熟悉 VMWare、Parallels、VirtualBox 或类似的东西,这就像拍摄 VM 的快照。或者考虑休眠或暂停您的计算机。)
事实上,如果你今天下载 Squeak 的一个版本,其中可能有已经运行了 30 年的对象。(这是 Smalltalk 和其他系统之间的另一个区别。Smalltalkers 认为,就像美酒一样,对象会随着年龄的增长而变得更好。这与 PHP 或 Ruby on Rails 等形成鲜明对比的是,对象在很久之后就会被丢弃每个网络请求。)
这种生动编辑最著名的例子之一来自关键的“Apple Demo”,当时史蒂夫乔布斯、Jef Raskin 和 Lisa 团队的其他成员于 1979 年访问施乐 PARC 以获得 Smalltalk 系统的演示:
演示中最好的部分之一是史蒂夫乔布斯说他不喜欢
blt
我们使用的 -style 滚动并询问我们是否以平滑连续的方式进行冷处理。在不到一分钟的时间里,Dan [Ingalls] 找到了所涉及的方法,进行了(相对较大的)更改,并且滚动现在是连续的!这让参观者,尤其是其中的程序员感到震惊,因为他们以前从未见过真正强大的增量系统。—<a href="http://Smalltalk.Org/smalltalk/TheEarlyHistoryOfSmalltalk_V.html#29" rel="nofollow noreferrer ">Smalltalk 的早期历史 作者:Alan Kay,HOPL-II,©1993 ACM
哦,是的,您确实没看错:Dan Ingalls 从实时系统中重写了实时系统中的视频驱动程序,而无需重新启动系统甚至暂停应用程序。在不到 60 秒的时间内。(我有没有提到编程语言和操作系统之间没有区别?)
可以在视频Self 中找到对活泼属性的最佳解释之一;电影(Self 是 Smalltalk 的衍生产品)。另外,看看 Dan Ingalls(Smalltalk 的原始设计者之一)的Lively Kernel,它基本上是 Smalltalk 想法到 JavaScript 的一个端口,在网页中运行。
正如我在上面所写的,这实际上是环境的一个特性,而不是语言。因此,即使我在这里使用 Smalltalk 作为示例,您也可以为任何语言实现一个生动的系统。我已经提到 Lisp 作为一种存在许多活跃环境的语言,并且我举了一个 JavaScript 的例子。APL 和 Forth 系统也很活跃。Factor是生动系统的一个很好的例子。最极端的例子可能是 VxWorks 实时操作系统,它包含一个用于 C(!) 的半活跃系统,该系统已被用于诊断和修复优先级反转问题,该问题会导致 Sojourner 漫游器在另一个 friggin上的虚假系统重置' 行星!
我认为这不一定是语言问题,因为它是 IDE 等问题。甚至像 C 这样的编译语言也有解释器。
如果您认为它会帮助您,您可能会为您正在编程的任何内容找到一个解释器。
Erlang允许您修改正在运行的程序,本质上能够在程序永不停止运行的情况下即时更新方法。
好吧,我正在一个业余爱好项目上做这样的事情。它使用名为 Eiffel 的静态类型编译语言。
与传统编译器的不同之处在于编译器作为服务器而不是命令行程序工作。它将所有数据解析在内存中,并真正对它们进行增量编译。连同一个全内存增量链接器,这个想法是让编译/链接/运行周期少于 2 秒。
但是是的,仍然存在一个问题,您只能使用基于图像的语言(如 Lisp 或 Smalltalk)来解决。代码始终以 Adam&Eve 开头,并且必须在程序中达到定义的情况。
但目前,如果您需要一种真正快速的可执行语言,这似乎是最好的折衷方案。活泼性要求浪费大量性能,因为必须以某种方式保留现有的对象结构,理论上是可以的,但实现起来非常复杂。
嗯,你真的想要这样的东西吗?!
假设您为您的 C 语言环境设置了这样一个系统。因此,您的编辑器已配置为在每次按键时检查它是否“导致有效代码”......如何?除非您的编辑器在解析代码时冗余地包含与编译器/解释器相同的所有逻辑,否则您将不得不调用编译器。(让我们忽略将文件保存到磁盘的开销,并想象您的编译器可以直接读取编辑器缓冲区的内容。)因此,在每次按键时它都会编译代码。您可能会在 99% 的时间里遇到错误,这仅仅是因为您还没有完成输入变量或关键字的名称。您的系统将如何知道错误是由于该错误还是由于“真实”错误?
此外,您如何在程序执行时将编辑合并到程序中?假设您的程序位于五个堆栈帧深的位置,并且您更改了传递给第一个函数的变量的值。如果不再次从第一个函数开始执行,就无法神奇地传播更改。但如果改变没有传播,它真的被纳入程序了吗?当然,调试器可以让您修改正在执行的程序中的现有变量,但您不能做诸如创建新变量或函数调用或控制结构之类的事情,所有这些事情您可能会在您的方案下做。
大多数解释语言都有一个读取-评估-打印循环(REPL),您可以在其中定义函数,然后将其存储在解释器的环境中,您可以按名称调用这些函数,甚至重新定义函数,以便之前定义的调用它们的函数然后将执行新版本。但即使在这种情况下,只要有提示,您的程序也不会执行,因此能够给它一些新的解释。