Lisp 是一种“不可能”的语言,迄今为止最复杂的 Malbolge 程序

2021-08-05 21:08:50

MalbolgeLisp 是用 Malbolge 编写的 LISP 解释器。截至 2020 年和 2021 年,这是有史以来最先进、最实用的 Malbolge 程序。它支持 LISP 通常倾向于支持的所有内容(如 cond、let、lambda 等)。 v1.1 版本大大提高了性能并减少了代码大小,同时添加了一些功能。 Malbolge 是一种公共领域的深奥编程语言。它被专门设计为几乎无法使用,通过反直觉的“疯狂操作”、三元算术和自修改代码。它建立在较早的、具有挑战性的深奥语言(如 Brainfuck)的难度之上,但将这方面发挥到了极致。尽管有这种设计,但还是可以编写有用的 Malbolge 程序(正如该项目所证明的那样)。 Malbolge 指令的作用取决于它们在源代码中的位置。运行后,它们被加密(因此要进行循环,必须在每次迭代后对其进行解密 - 听起来很难吗?)。这就是如何发现所谓的指令周期——已经观察到某些位置上的一些指令形成循环周期,这是 Malbolge 编程的基础。迄今为止,在 Malbolge 制作的最复杂的程序包括加法器、“99 瓶啤酒”程序和“你好,世界!”程序(最初由使用遗传算法的 Lisp 程序生成)。 MalbolgeLisp 使用 Malbolge 的一种特殊变体,称为 Malbolge Unshackled。由于多种原因,编程要困难得多:Malbolge Unshackled 允许旋转宽度可变,它随着 D 寄存器中的值而增长,并且由于初始旋转宽度未知,您必须对其进行探测(否则 * 返回不可预测的结果) 如果旋转宽度未知,则不能加载大于 3^4-1 的值,但以 1 trit 开头的值除外

为了克服这个问题,你需要一个循环来探测旋转宽度,这可能超出了大多数人的理解规范说值 0t21 应该用于打印换行符,但理论上不可能在没有读取行尾或之前来自 I/O 的文件结尾。 Malbolge Unshackled 实际上是可用的,因为它(正如这个项目所证明的)图灵完备。默认的 Malbolge 旋转宽度 (10) 限制了可寻址内存,足以让它变得很酷。发行包包括适用于 Windows 的解释器二进制文件(x64,一种针对内存消耗进行了优化,一种针对速度进行了优化),以及一些适用于 Linux 的解释器(名称应该是不言自明的,也是 x64)。如果您运行的不是 x64 机器,则必须自己编译 fast20.c。根据我的观察,最好的结果是由 GCC 产生的,并且代码已经过调整以在使用它编译时表现良好。 malbolgelisp-v1.1.mb 是解释器的源代码。启动解释器后,您应该会看到一个横幅(包含版本、点命令等)。当前可用的点命令如下: 任何其他命令(在 Windows 上发送 EOF - ^Z,在 UNIX 上发送 ^D 将终止解释器)。输入一个表达式后(例如 - (+ 2 2)),解释器应该打印出类似这样的内容:

这些点用于表示代码正在被解析(在管道字符之前)或正在评估(在管道字符之后)。点数不对应于任何一致的时间量。 MALBOLGELISP V1.1 (2020-2021, PALAIOLOGOS) DOT 命令:.F(特征) .A(uthor) .R(eset) .M(emory) % .F ' + - < > = ! &| * /define defun lambda cond print atom cons car cdr if let iota size nth MalbolgeLisp 相当慢。如果您想在速度更快、与 MalbolgeLisp 基本兼容的环境中测试程序,请尝试 x86Lisp——一个基于这种方言的 Lisp 解释器,压缩成一个 2.1KB 的 Windows .exe 文件。下面给出了一些在 MalbolgeLisp 中执行算术的例子。注意:不支持有符号整数。 % (+ 2 -1) ......|..... 1 % (* 3 3) ......|. .... 9 % (% 5 2) ........|... 1 % (> 5 6) ........ ..|... 0 % (< 5 6) ........|... 1 % (= 6 6) ........ ......|...... 1 % (! (= 6 7)) ......................|.. ..... 1 % (/ 25 5) ..... 5 % (- 4 3) ..... ...|... 1 % (& (= 2 2) (= 3 3)) ................................... .........................|................. 1 没有>= 或<=,必须分别取反< 和> 来实现。单个 & 和 |分别是逻辑与和或。 MalbolgeLISP 还支持词法范围(一个函数看到它的词法祖先的环境,而不是它的调用者)lambdas。例如:

% ((λ (n) (* nn)) 3) ................................... ....|......... 9 % ((lambda (m) ((lambda (n) (+ mn)) 2)) 2) ........ …………………………|…… ........ 4 那么如何定义函数呢?好吧,从上面的例子中可以得出一个合乎逻辑的结论: % (define succ (lambda (x) (+ x 1))) ........................ ......................|.. (lambda (x) (+ x 1)) % (succ 2) ....... ....|......... 3 因为定义 lambda 每次都输入有点烦人而且看起来不太好,我们可以使用更好的语法 - defun: % (defun succ (x ) (+ x 1)) ...................................|. (lambda (x) (+ x 1)) % (succ 2) ...........|........ 3 等号函数 (=) 也适用于原子和列表.必须引用列表以防止解释器评估它们: % (= test test) ...................|...... 1 % (= '( 1 2 3) '(1 2 3)) .....................|.. ..... 1%

size 产生作为参数传递的列表的大小。使用 size 和 iota,我们可以为数字创建一个非常低效的恒等函数:% (defun id (x) (size (iota x))) ................... ......................|. (lambda (x) (size (iota x))) % (id 6) ...........|........ 6 % .M 233B USED % # 那是浪费了很多内存! nth 生成列表的第 N 个元素。它使用与 iota 相同的索引,如下所示: 现在让我们尝试使用原子。我们可以使用 print 打印一个原子或一个列表,我们可以使用 atom 函数检查某个东西是否是一个原子。请注意,print 是一个标识函数,具有打印表达式值的副作用: % (atom null) ...........|..... 1 % (atom '(1 2 3)) 。 ...................|..... 0 %(打印你好)........|..你好你好 % (print '(1 2 3)) .....................|... (1 2 3) (1 2 3) % 让我们看看了解汽车和 cdr 的工作原理。 car 获取列表的头部(第一个元素),而 cdr 获取列表的尾部(除第一个元素之外的所有元素): % (define list '(1 2 3)) ..... ................|.. (1 2 3) % (汽车列表) ................|..... 1 % (cdr列表).........|.. (2 3)

if 函数正好需要 3 个参数 - 条件、判断条件是否为真的表达式以及判断条件是否为假的表达式。一个小例子:% (defun gt10 (x) (if (> x 10) (print yes) (print no))) ..................... ...................................................| . (lambda (x) (if (> x 10) (print yes) (print no))) % (gt10 5) ...........|......... .... no no % (gt10 16) .....|................. yes yes 如果你需要在一个地方有很多 ifs ,可以考虑使用cond。它需要一个列表列表(条件结果),可选地包括一个(结果)列表。使用 cond,我们可以创建一个三路比较函数(对于 > = <,返回 2 1 0): % (defun <=> (xy) (cond ((> xy) 2) ((< xy) 0) (1))) ..................................... ......................................|. (lambda (xy) (cond ((> xy) 2) ((< xy) 0) (1))) % (<=> 5 6) .........|. ................................... 0 最后的内建函数是 let。它允许我们将名称绑定到表达式(以便以后不会重新评估它们)。例如: (defun filter (fl) (cond ((= l null) null) ((f (car l)) (cons (car l) (filter f (cdr l)))) (1 (filter f (cdr) l)))))(defun append (ab) (if (= null a) b (cons (car a) (append (cdr a) b) )))(defun append3 (abc) (append a (append bc) ))(defun list>= (m list) (filter (lambda (n) (! (< nm))) list))(defun list< (m list) (filter (lambda (n) (< nm)) list ))(defun qsort (l) (if (= null l) null (append3 (qsort (list< (car l) (cdr l))) (cons (car l) null) (qsort (list>= (car l) ) (cdr l))))))(qsort '(6 8 1 0 6 8 2)) (defun insert (il) (if (= null l) (cons il) (if (> i (car l)) (cons (car l) (insert i (cdr l))) (cons il))))(defun insertsort (l) (if (= null l) null (insert (car l) (insertsort (cdr l))) ))(插入排序 '(6 8 1 0 6 8 2))

还有一堆我自己写的程序——一个计数器、幂函数、阶乘函数和列表的最大值。 (defun counter (x) (cond ((< x 10) (counter (print (+ 1 x)))) (print ok)))(counter 1)(defun power (xy) (if (= y 0) 1 (* x (power x (- y 1))))))(power 3 5)(defun fac (n) (if (= n 0) 1 (* n (fac (- n 1)))))(fac 6)(defun max (l) (if (= null l) 0 (let (m (max (cdr l))) (if (> (car l) m) (car l) m))))(max ' (2 5 1 9 10))