Rhci:用 Ruby 实现的轻量 GHCi,单文件启动 3 ms
Show HN: Rhci – A Small GHCi in Ruby
Rhci 是用 Ruby 实现的小型 GHCi,提供与 GHCi 一致的 Haskell REPL 语义,提示符 3 ms 出现、单行表达式 25 ms 求值。
Ruby 中的小型 GHCi:一个具有 GHCi 语义的 Haskell REPL。
一个二进制文件,或一个 Ruby 文件。3 毫秒内出现提示符,25 毫秒内执行一行代码。
$ 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