突发奇想:用 Geiser 写一个 Emacs NewLisp REPL?

NewLisp 是一种脚本解释型的 Lisp 方言,特点是默认使用动态作用域(Dynamic Scoping),和 Scheme 类似的 define 过程定义,超快的运行速度(在脚本语言中算顶级),极度轻量化(600 KB 出头,低于大多 Scheme 实现),强大的调用动态链接库能力。

NewLisp 官方的 IDE 居然是基于 Java 的。有点无语。

然而,我发现 Emacs 上只有个 newlisp-mode。然后这个作者自己搞了个功能有限的 swank-newlisp

所以我现在正在看 Geiser 文档,准备改造个 REPL 出来。Geiser 默认同时支持多种 Scheme 实现,比 SLIME 稍微友好一点,有很多能利用的宏。看起来 NewLisp 是能用 TCP Server 的形式和 Geiser 交互的。跳转啊 DEBUG 啊也是靠预先定义的命令字符串,都是高度可适应的。最模糊的地方可能就是补全。就是怕 Geiser 的作者看到我用 Scheme REPL 搞 NewLisp 会把我打死。

可能稍微要参考下那个 Java 的 IDE。

大家有什么好的想法嘛?

2 个赞

想法是 elisp、clojure 或者 racket。

然后,编程这个事,第一步还是先掌握原理和算法思想(当然这里开始可能就要考虑机器结构以及平台结构了),然后才是具体的语言层面、机器层面的实现。

你来提问就说明你对之后要做的事有点恐慌。

emacs-china 上有个我遗留的未完成事项:https://github.com/emacs-china/EGO/issues/91

有兴趣可以试一下:grimacing:

1 个赞

上 NewLisp 这个小众的小众是因为喜欢 Emacs Lisp 的动态作用域,又想要个轻量脚本解释器。

角色扮演编程流派的我也就只会组装工具思维。从头造起就有点虚了。

主要是没有更博客的心就没有重写 EGO 的心啊

大家都是这样我就放心了。

我静态网站生成没太大兴趣啊。

轻量的脚本解释器用 shell 就不错,用 NewLisp 可能就过于复杂了。

编程语言实际上是构建在人与计算机之间的通信协议,很多编程问题看似工程问题,实为社会问题,我以前也考虑过 NewLisp,但是受众不多,难以推广。 而 emacs 能用 elisp 造出如此多有趣的功能很大程度上也源于它的社区,另外 elisp 也能当作脚本。

EGO 2.0 是个整体性工程,用组装工具的方法已经不合适了:sweat_smile:

说到社会问题……又想起了当初 Emacs 作者们忽悠他们办公室的秘书说用 Lisp 写 Emacs 扩展不算编程的事。

我要做的就是把他们做过的再做一遍。可惜那时候人多淳朴啊,现在都不好直接忽悠了。

所以每个用 Emacs 的人都是社区的宝贵财产啊。

1 个赞

EGO 2.0 要是能如计划完成,那么它在 Emacs 社区里的效果应该不亚于 hexo 在 node 社区里的效果吧

org-mode vs markdown S-exp vs yaml

作为一个横跨了 emacs 和 web 浏览器前端的桥梁,用它来做大新闻应该说是挺不错的

不过我目前对前端和 emacs 都算手生了…

我有一个大胆的想法(请开始你的表演)。

用一个 Org 文件和一个类似 blog-admin 的提供管理按键的子模式来管理源文件和生成的文件。每个文件下方生成属性。通过 TODO 状态来处理 Publish 状态。保存以后开始处理文件。

然后我觉得用 pandoc 作为可选的导出后端也不差。因此能增加几个 markup 语言的支持,何乐而不为?

Org-mode 提供的接口有几个问题:

  • org 文件内容和 org 文件的属性信息(譬如说作者、文章标题等等)放在了一个 org 文件里,这样要提取 org 文件的属性信息就必须载入整个 org 文件;而 hexo 管理 md 文件的方式则是将 md 文件的属性信息放在另一个 yml 文件里,这样在只需要文件属性的场合,只用载入小小的 yml 文件就好了

  • org 文件作为结构化(就是嵌套)文本格式是有缺点的(这个缺点详述就有点麻烦了),所以不适于用于 xml、yml(yaml)、json 和 S-exp 等结构化配置文件的场合,但是怼 markdown 这种简单标记文档格式还是合适的

而 blog-admin 的问题则是要兼容多种后端搞前后端分离,但是实际上应该算是 ctable 结合 f 在管理文件列表上的一个应用,但是由于深入到了 export 层面,这就要面临融合各种后端 export 方式差异的问题。而在我看来,这种融合基本意味着对后端 export 功能的省略,而且 EGO 2.0 有一个即时预览 html 文件的要求。

至于 pandoc——用它意味着要连带一整套 haskell 环境,这个代价太大了,而且 org 对多格式导出的支持已经算非常不错了。

前两个问题,如果有一个简易 indexer 用匹配的方法提取重要信息写入一个 lisp 格式的目录文件就可以解决了。各种标记语言和 Lisp 比较就显得累赘而不易读。不管用纯 Lisp 还是为了考虑速度用别的脚本都没关系。甚至用 go 来个 binary 也行。

实时预览我不算了解,是写入到临时目录里面吧? 但是用 vmd 的经验让我觉得 Emacs 不太适合完全搞实时的,太耗性能。把生成文件钩在手动保存/自动保存就很好了。当然这个做成可选的就差不多了。

blog-admin 只是把生成 html 和推送交给后端了,文件管理都是要自己实现的。

Pandoc 只是作为一个命令行的后端。有其他干净的选择当然可以。我觉得 Emacs Lisp 还是安心当胶水比较好,如果生成站点的时候宕机了就尴尬了。不管怎样,把后端接口留给用户总是好的。

我还是喜欢那种 Blading fast 的体验,Emacs Lisp 还是负责对速度没要求的部分好。(看了下发现不少静态网页生成都用 pandoc,看来用这个当后端的确没什么特色)

lisp 格式的文件通常就叫 S-exp 了

[quote=“LdBeth, post:9, topic:3043”] 把生成文件钩在手动保存/自动保存就很好了。 [/quote]这样也算一种实时吧,可以看下 org-iv,就是这样实现的

elisp 用的好也是很快的……不好意思,最后这个帖子全歪了:grimacing:

一直找不到newlisp ide之类的工具,支持楼主的做法。

看了看指南学了一下。果然是梦幻级别的语言。感觉这个比 Scheme 还要简单。

就生成静态页面而言,速度影响不大,比如我的网站就用的是 Emacs 生成的(配合 Nanoc 和 Org mode),不算缓存,从头生成整个站点需要 1.56 s,换成 Pandoc 估计能节省一秒钟,但影响不大,只要不是太慢就行。

~/src/xuchunyang.me (master *)$ nanoc
Loading site… done
Compiling site…
Starting Emacs daemon.
0
      create  [0.93s]  output/blog/2017/06/open-file-with-local-emacs-in-ssh/index.html
0
      create  [0.07s]  output/blog/2017/06/send-email-from-command-line-with-emacs/index.html
0
      create  [0.12s]  output/blog/2017/05/use-nanoc-with-org-mode/index.html
      create  [0.01s]  output/feed.xml
      create  [0.00s]  output/htmlize.css
      create  [0.01s]  output/index.html
      create  [0.00s]  output/main.css
      create  [0.00s]  output/main.js
      create  [0.01s]  output/sitemap.xml
      create  [0.00s]  output/tags/emacs/index.html
      create  [0.00s]  output/tags/nanoc/index.html
      create  [0.00s]  output/tags/org/index.html
      create  [0.01s]  output/tags/emacs/feed.xml
      create  [0.00s]  output/tags/nanoc/feed.xml
      create  [0.00s]  output/tags/org/feed.xml

Site compiled in 1.56s
1 个赞

奇怪,真奇怪,你怎么会喜欢动态作用域呢? 你是不是elisp写多了。。。

实际上,动态作用域是已经扔进编程语言发展的历史垃圾堆的东西了,emacs-lisp是历史遗留问题。 我写elisp还喜欢用lexical-let呢

elisp作为一个早期的lisp,其实缺点很多。在这其中,动态作用域是硬伤。

至于现代的比较完美的lisp,可以参考scheme。

自由的代价是要处处小心。

我的意见是,至少得问问我要不要用动态作用域这个有时候很方便的东西。elisp 做的很好,是可选的。虽然这是为了兼容依旧代码以及有些时候闭包还是有点问题。

在NewLisp里,比如某些时候:

(define (foo) 1)
(define (bar) (+ 2 (foo)))

(bar)
;; => 3

(let ((foo (lambda () 2)))
  (bar))
;; => 4
;; 在 Scheme 里面就会是 3

很方便的局部重定义函数。而且有命名空间的配合,动态作用域还没指针可怕。

这篇文章介绍了 NewLisp 的特点:

http://m.oschina.net/question/565376_62226

其他包括引用未赋值的变量不会报错,只会返回 nil;能直接引用动态库;使用唯一引用内存管理来避免垃圾回收地狱,而且因此只需要一个 eq 操作符就能判断是否相等。作为脚本引擎,加载配置和运行脚本的区别只在于有没有声明 (exit)

非常抱歉,我不是很赞同你的一些看法。

引用未赋值的变量不会报错

这是一个很不好的设计,因为会导致大量的变量名拼写错误无法暴露出来。

使用唯一引用内存管理

也就是说变量其实是值类型对吗?那篇文章说没有垃圾回收器,那为什么不需要gc呢?首先这个语言一定能表达链表对吧?能表达链表一定就能表达环形链表,就会出现环形引用,出现环形引用要么手动管理内存要么用gc解决。

最后谈一下动态作用域。你的示例代码,看似很方便,但是请注意,这会使得一个函数依赖的符号变得非常非常多,函数的行为变得很不可控。你根本不清楚更高级的调用者是否重定义了函数的依赖的符号。

如果想达到演示代码里的这种效果,简单一点的方法是使用全局变量来定义需要改变的那个符号。更好的方法,是把需要改变的符号从参数传入,或者放到某个数据结构里去。

那篇文章说采用动态作用域是因为性能好。。。我不是学PL的对这个结论不太清楚。个人的看法是,首先,为了一点儿性能牺牲使得代码更不清晰不是很值得;其次,如果是编译语言的话,编译器在编译器有充足的时间去优化那一点儿性能损失;如果是解释型语言的话,解释器语言从来都不在乎那一点儿性能损失。

我看了一下那篇文章,觉得:

f表达式与求值

lisp的宏没什么经验,这个感觉是一种使用正则序求值的函数,直觉告诉我这个特性还是不错的。

这篇文章详细的讨论了动态作用域和词法作用域: http://www.yinwang.org/blog-cn/2013/03/26/lisp-dead-alive

作者是专业的PL出身,因此他对此的观点很有参考性。

1 个赞

看过这篇啊……应该说我发主题之前还看了一遍,发这贴的时候也看了一遍。 当初就是看他早期的博客被安利了 TeX 和 Lisp。不过众所周知王垠是 Elisp 黑,Shceme 粉。这个是由个人经历个人喜好决定的,就和有人爱 iOS,有人爱 Android 差不多。

我觉得有必要重新审视下动态作用域的优点。这好歹算是 Lisp 的特色了。

未赋值的变量不会报错,主要是为了减少写参数列表的麻烦。在NL甚至可以这样:

(define (foo x y , p v) body...)

其中逗号用来在视觉上分隔必要参数和局部变量。 实际上逗号也是被当作正常参数处理的。

然后就省去了繁琐的 let 结构。

为什么不需要垃圾回收,是因为没有联系到符号的内存在表达式求值结束后立马被清理,而不是被标记以后等着被回收器处理。

然后 NL 应该不支持环形引用。它不支持 cons cell (它有个 cons 函数,但只是用来把两参数组合成列表。),而据我所知 CL 或 Elisp 都是通过 cons cell 实现环形引用的。Shceme 我不了解。能否给个 Shceme 的例子?(我拿 guile 跑 Elisp 的例子,却 Loop 住了)

解释器语言一点都不在乎那一点性能损失。

这话说出来……有点道理。CL Scheme 乃至 Elisp 都是能用编译器优化的。

你反驳的点,我理解为 “不安全,不可控”。不过 NL 本身并不是设计成过于通用的语言。在 NL 设计的应用场合,几百行层次的程序,有各种辅助机制下,动态作用域不会带来太大的困扰,反而可以 让程序更简练 ,和花几十行搞个数据结构,每个函数加几个只用一次的参数相比,我觉得有风险是值得的。就算有内存泄漏也有很多人用 C 嘛。

简言之,有动态作用域不会导致写不出程序,没有也不会导致多少麻烦。

不过众所周知王垠是 Elisp 黑,Shceme 粉

你可以说它是微软黑,linux粉,这我赞同。但是王垠是 专业研究programming language的,这篇文章可以认为他从专业的角度阐述专业的看法,用 “黑” “粉”这样的词概括恐怕有些不合适吧?

我觉得有必要重新审视下动态作用域的优点

从王垠的文章可以看出,PL专业领域已经评估过了动态作用域和词法作用域,现代的任何一门语言不约而同的选择了词法作用域(C++, java, python,j avascript, scheme, lua, 甚至php),这是不是说明了什么?

未赋值的变量不会报错,主要是为了减少写参数列表的麻烦

个人觉得,为了少写一点点的代码,大大增加犯错误的概率和debug的难度,是很不划算的。 想想为什么静态类型那么麻烦还要手写类型名称好麻烦,没有鸭子类型搞什么继承好啰嗦,却有很多语言采用?因为编译器能帮你检查出一大堆弱智的错误出来。

然后 NL 应该不支持环形引用

不支持环形引用。。。。。。为了内存管理而去阉割语言特性,不太好吧?例子很好举,一个数据结构包含一系列的元素,链表,vector,hash表都行。其中一个元素引用这个数据结构本身,就是最简单的环形引用。

在 NL 设计的应用场合,几百行层次的程序,有各种辅助机制下,动态作用域不会带来太大的困扰,反而可以 让程序更简练 就算有内存泄漏也有很多人用 C嘛

  1. 你也承认,由于动态作用域的局限性,NL顶多拿来写写几百行的小脚本而已。
  2. 几百行的小脚本,想让程序更简练,不花几十行搞个数据结构,并不是动态作用域的专利。我前面说了,使用 全局变量 也可以。
  3. 一些底层的需求只能用C的手动管理内存。可是动态作用域有什么非用不可的地方吗?而且,C++的出现也是在努力的尝试用更好的语言特性去降低内存泄漏,比如RAII。

简言之,有动态作用域不会导致写不出程序,没有也不会导致多少麻烦 我的意思是,词法作用域比动态作用域要优秀的多。

1 个赞

这个话题要等以下圣战有结果才能下结论。

  • 静态类型好还是动态类型好
  • Ruby 好还是 Python 好
  • Scheme 是进步还是妥协
  • C or C++?
  • 如何评价王垠(喂)

容易造成又一次大战。 所以在论坛里的讨论还是点到为止吧。

不过我还是学到了不少东西,比如现代 Lisp 是靠闭包导出 AST 来实现编译的。 JS 可以模拟动态作用域。所以 NL 可以转译 为 JS 。 至于我原来的回复,还是私信里面聊吧。

1 个赞