跳到正文
Hacker News· rowbin·· 6 天前AI 评分22

Rhci:用 Ruby 实现的轻量 GHCi,单文件启动 3 ms

Show HN: Rhci – A Small GHCi in Ruby

AI 导读

Rhci 是用 Ruby 实现的小型 GHCi,提供与 GHCi 一致的 Haskell REPL 语义,提示符 3 ms 出现、单行表达式 25 ms 求值。

正文 · AI 翻译

Ruby 中的小型 GHCi:一个具有 GHCi 语义的 Haskell REPL。
一个二进制文件,或一个 Ruby 文件。3 毫秒内出现提示符,25 毫秒内执行一行代码。

MIT License CRuby 2.7 to 3.4 mruby 4.0 Tested against GHC 9.6

源码和发布版本:GitHub / CodeFloe


$ rhci
rhci> xs = [x * x | x <- [1 .. 10], odd x]
rhci> sum xs
165
rhci> :t foldr
foldr :: Foldable c => (a -> b -> b) -> b -> c a -> b
rhci> import qualified Data.Map as M
rhci> M.fromListWith (+) [(c, 1) | c <- "mississippi"]
fromList [('i',4),('m',1),('p',2),('s',4)]
rhci> show (0.1 + 0.2)
"0.30000000000000004"
rhci> [1, 2, undefined]
[1,2,*** Exception: Prelude.undefined

功能

  • GHCi 的语义:带类型类的 Hindley-Milner 类型推断 (字典传递、构造器类、与 GHCi 相同的默认规则)、 带共享的惰性求值,以及 show 输出与 GHC 完全一致,从 Double 格式化到 Int 环绕
  • 宁可失败也不猜测:任何 rhci 无法完全按照 GHCi 方式完成的事情都会被拒绝,并给出明确的 unsupported: 错误
  • 真正的 Prelude:从 GHC 的 base 移植而来,外加 Data.List、Data.Char、 Data.Maybe、Control.Monad、Data.Map、Data.Set、Data.IORef、Data.Bits、 Data.Ratio、Numeric、Text.Printf、Text.Read 和文件 IO
  • GHCi 工作流::type、:kind、:info、:load/:reload 多模块程序、:{ ... :} 块、it、在提示符处导入、 带历史记录和 Tab 补全的行编辑器、Ctrl-C 中断
  • 脚本和单行命令:rhci -c EXPR 求值后退出,-i 像 interact 那样将结果应用于标准输入,而 -r FILE 像 runghc 那样运行文件的 main,因此一行 #! 就能让 Haskell 文件 可执行
  • 小巧且自包含:一个可移植的可执行文件,完全无 依赖(15 MB,为 Linux、macOS、Windows 和 BSD 构建),一个 7 MB 的静态 Linux 二进制文件,或一个 Ruby 文件,适用于从 2.7 到 3.4 的任何 CRuby;作为库,它可以嵌入 Ruby 程序,并对 求值步数、栈深度、时间和 Integer 大小进行限制

用途

快速尝试某些东西。words 对制表符做什么,这个 fold 会终止吗,那个组合的类型是什么。GHCi 能回答所有这些, 但它随 GHC 一起提供,安装需要数 GB, 提示符出现前有明显等待,而且在你刚刚 ssh 进入的机器上 它并不存在。rhci 是一个你复制过去的文件,提示符在 毫秒内出现,答案就是 GHCi 会给出的答案,精确到 输出的格式以及哪些输入是类型错误。在它 无法承诺这一点的地方,它会明说。

Haskell 脚本。rhci -r script.hs,或文件顶部的 #!/usr/bin/env -S rhci -r 行,用 getArgs 上的参数运行 main, 而 -i 将纯函数变成标准输入上的过滤器。这适用于 你原本会用 shell 或 Ruby 写的那种脚本,不适用于 长时间运行或计算密集的程序:rhci 是 Ruby 中的解释器, 在紧凑循环上比编译后的 GHC 慢约 100 倍。

可移植性和大小。每个发布版本包含三个文件:一个 Cosmopolitan 可执行文件,在 Linux、macOS、Windows 和 BSD 上原样运行, 支持 x86-64 和 ARM64,不需要 libc;一个静态 Linux 二进制文件,适用于任何 年代的系统;以及一个 Ruby 文件,适用于任何有 Ruby 的地方。它们都不 需要工具链、包或除自身以外的任何文件。

Haskell 作为嵌入式脚本语言。作为 Ruby 库,rhci 为 Ruby 程序(或任何嵌入 Ruby 的东西)提供一个 Haskell 解释器, 其会话彼此隔离并与宿主隔离, 对求值步数、栈深度、时间和 Integer 大小有限制,并且 文件访问可以关闭。

构建方式

  • 两个运行时,一份源码。相同的 Ruby 代码在 CRuby 和 mruby 上运行。独立二进制文件是编译进 rhci 的 mruby, 其求值循环用 C 运行。
  • 真正的编译器流水线,保持小巧。包括 Haskell Prelude 在内约 23 000 行。
  • 快照式启动。编译后的 Prelude 以构建时快照的形式发布。启动时只解码典型输入所需的部分,其余部分按需加载,或在您输入时在后台加载。
  input --> lexer + layout --> parser --> renamer --> type checker --> desugar to Core
                                                     (classes, kinds,     |
                                                      defaulting)         v
                    output <-- lazy graph-reduction machine <-- simplifier + lowering
                               (Ruby, or C in the binaries)

设计说明见 docs/ARCHITECTURE.md。

安装

每次发布,在 GitHub 或 CodeFloe 上都有这些文件, 全部自包含(Prelude 已嵌入);将其中一个复制到您 PATH 上的任意位置:

文件 运行于
rhci-1.0.0 Linux、macOS、FreeBSD、OpenBSD、NetBSD;x86-64 和 ARM64。一个 Cosmopolitan 可执行文件,无需 libc。目前已在 Linux x86-64 上验证;欢迎来自其他系统的报告
rhci-1.0.0.exe Windows,x86-64 和 ARM64:同一个文件,重命名以便 Windows 运行它
rhci-1.0.0-linux-x86_64-musl 任何 x86-64 Linux,无论多旧:一个静态二进制文件
rhci-1.0.0.rb 任何带有 Ruby 2.7 到 3.4 的环境(chmod +x 它,或 ruby rhci-1.0.0.rb)

rhci 唯一写入的文件是它的历史记录,~/.rhci_history。

构建

要求:带 rake 的 Ruby(构建使用 CRuby 3.4 测试,版本固定在 mise.toml 中)、git 和 C 编译器。构建过程会自行获取 mruby 4.0.0。

rake mruby      # build/rhci/bin/rhci: a binary for this machine's glibc
                # build/rhci.rb: the compact CRuby build (also: rake compact)
rake            # lint, build, the tests on CRuby and mruby (rake bench: startup budgets)
rake release    # the release files into dist/, with SHA256SUMS (needs curl, unzip, podman)

docs/DEVELOPMENT.md 有详细信息,包括发布构建是如何制作的。

用法

rhci [FILE.hs...]                 interactive session, FILEs loaded
rhci [FILE.hs...] [-i] -c EXPR [ARG...]   apply pure EXPR to ARGs (Haskell expressions)
rhci [FILE.hs...] [-i] -x EXPR [ARG...]   run IO action EXPR; ARGs via getArgs
rhci -r FILE.hs [ARG...]          run FILE's main with ARGs, like runghc
rhci -h | --help | --version
$ rhci -c 'sum [1..100]'
5050
$ rhci -c 'map toUpper' '"hello"'
HELLO
$ printf 'one\ntwo\n' | rhci -i -c 'unlines . map reverse . lines'
eno
owt
$ rhci -x 'getArgs >>= print' a b
["a","b"]
$ cat hello.hs
#!/usr/bin/env -S rhci -r
import System.Environment
main :: IO ()
main = getArgs >>= putStrLn . unwords . ("hello" :)
$ ./hello.hs world
hello world

字符串按原样打印,其他值使用 show 打印。退出状态为 0 表示成功,1 表示 Haskell 异常,2 表示用法错误,3 表示解析、作用域或类型错误,4 表示不支持的内容。

命令

命令 操作
expression 求值并打印;IO 操作会被执行。结果为 it
x = ...、f x = ...、data、class、instance 在提示符处定义
:{ ... :} 多行输入
:type EXPR 表达式的类型(:t 可用,任何无歧义的前缀也可用)
:kind TYPE 类型的种类
:info NAME 名称是什么:其类型、类、实例、结合性
:load FILE... / :reload 加载模块(提示符处的定义会被丢弃)/ 再次加载它们
:quit 离开(或 Ctrl-D)
:?、:help 列出命令

Ctrl-C 中断求值,包括被阻塞的 getLine。

作为 Ruby 库

require_relative 'rhci'   # build/rhci.rb
require 'stringio'

out = StringIO.new
session = Rhci::Session.new(stdout: out, limits: { steps: 10_000_000 })

session.eval('xs = [x * x | x <- [1 .. 10]]')
session.eval('sum xs').status        # => :ok, and out.string is "385\n"
session.eval('head []').message      # => "Prelude.head: empty list"
session.eval('length [1 ..]').status # => :limit

会话之间相互隔离,也与宿主隔离。限制涵盖求值步数、栈深度、挂钟时间和 Integer 的大小,files: false 会关闭文件 IO。

覆盖范围

Haskell 2010 及类型类(单参数,包括构造器类)、Show、Read、Eq、Ord、Enum 和 Bounded 的 deriving、记录、带导出和导入列表的模块、where/let/守卫/case、列表推导、do 记法(带 MonadFail)、bang 模式、带 GHC2021 多态种类的种类推断,以及 GHCi 提示符的规则:扩展默认化、无单态限制、it、重定义。

有意不支持,并给出明确错误:除 bang 模式和多态种类之外的 GHC 扩展(GADTs、类型族、rank-n 类型、多参数类、存在类型……)、32 位 Float、元组节、非 ASCII 标识符、default 声明、外部导入,以及除上面列出的 base 和 containers 模块之外的包。

对照 GHC 测试

rhci 给出的每个答案都旨在与 GHCi 一致,因此它对照真实的 GHC 9.6 进行测试:

  • 一个预言机,包含约 1 700 个手写探针及 GHCi 记录的答案,由 rake 在两个运行时上运行。
  • 差分模糊测试:约二十个生成器将 rhci 与 GHCi 或 runghc 在随机输入上进行比较:带有暴露求值顺序的错误值的表达式、完整的多模块程序、GHCi 会话、默认化、64 位边缘的数字、用户数据类型和类、损坏的 read 输入、故意的类型错误、词法陷阱、惰性、种类以及库的广度。超过 140 000 个生成的输入和 2 800 个程序在 CRuby 和 mruby 上均一致。
  • exercism Haskell 赛道:其参考解答和测试套件在 rhci 和 GHC 上运行。

test/fuzz/README.md 列出了每个模糊测试器覆盖的内容以及它发现了什么(早期发现相当多),docs/DEVELOPMENT.md 则说明了项目的组织方式。

致谢

由 Leon Becker 设计;实现主要由 Claude Code 完成。项目的结构、范围和设计决策由我负责;大部分代码本身是在我的指导下由 Claude 生成的。

相关项目

  • GHCi:真正的实现,也是 rhci 所参照测试的对象。除了快速检查和小脚本之外的任何用途都请使用它。
  • Hugs:经典的小型 Haskell 解释器,已不再维护。
  • MicroHs:一个小型 Haskell 实现,编译为组合子,并带有自己的 REPL。

许可证

rhci 采用 MIT 许可证。参见 LICENSE。

部分内容源自其他项目,其许可证适用于这些部分(详情见 THIRD_PARTY_NOTICES):

组件 用途 许可证
GHC 的 base 库 prelude/Prelude.hs(移植) BSD 风格
containers prelude/Data/Map、prelude/Data/Set(移植) BSD-3-Clause
Unicode 字符数据库 lib/rhci/unicode_data.rb(生成) Unicode License v3
mruby 独立二进制文件(不在本仓库中) MIT
Cosmopolitan Libc 可移植的 rhci 二进制文件 ISC
musl rhci-linux-x86_64-musl 二进制文件 MIT

来源:Hacker News · github.com