cd /news/developer-tools/show-hn-rhci-a-small-ghci-in-ruby · home › topics › developer-tools › article
[ARTICLE · art-149316] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Show HN: Rhci – A Small GHCi in Ruby

Developer beleon released rhci, a Haskell REPL written in Ruby that reproduces GHCi's semantics, offering a prompt in 3 ms and a one-liner in 25 ms. The tool ships as a 15 MB Cosmopolitan executable for Linux, macOS, Windows and the BSDs, a 7 MB static Linux binary, or a single Ruby file supporting CRuby 2.7 through 3.4, and refuses anything it cannot evaluate exactly as GHCi would with an explicit 'unsupported:' error. rhci is roughly 23,000 lines including a Prelude ported from GHC's base, and on tight loops runs about 100 times slower than compiled GHC.

read8 min views2 publishedOct 11, 2026
Show HN: Rhci – A Small GHCi in Ruby
Image: Michielbdejong (auto-discovered)

A small GHCi in Ruby: a Haskell REPL with GHCi's semantics.

One binary, or one Ruby file. Prompt in 3 ms, a one-liner in 25 ms.

Source and releases: 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's semantics: Hindley-Milner type inference with type classes (dictionary passing, constructor classes, defaulting as GHCi does it), lazy evaluation with sharing, andshow output that matches GHC exactly, fromDouble formatting toInt wrap-around
  • Fails rather than guesses: anything rhci can't do exactly as GHCi would is refused with an explicitunsupported: error
  • A real Prelude: ported from GHC's base, plus Data.List, Data.Char, Data.Maybe, Control.Monad, Data.Map, Data.Set, Data.IORef, Data.Bits, Data.Ratio, Numeric, Text.Printf, Text.Read and file IO
  • The GHCi workflow::type ,:kind ,:info ,:load /:reload of multi-module programs,:{ ... :} blocks,it , imports at the prompt, a line editor with history and Tab completion, Ctrl-C to interrupt
  • Scripts and one-liners:rhci -c EXPR evaluates and exits,-i applies the result to standard input likeinteract , and-r FILE runs a file'smain likerunghc , so a#! line makes a Haskell file executable
  • Small and self-contained: one portable executable with no dependencies at all (15 MB, built for Linux, macOS, Windows and the BSDs), a 7 MB static Linux binary, or a single Ruby file for any CRuby from 2.7 to 3.4; as a library it embeds in Ruby programs with limits on evaluation steps, stack depth, time and Integer size

Trying something quickly. What does words do with tabs, does this fold terminate, what's the type of that composition. GHCi answers all of that, but it comes with GHC, which is gigabytes to install and a noticeable wait before the prompt, and it isn't there on the machine you just ssh ed into. rhci is one file you copy, with a prompt in milliseconds, and the answer is the one GHCi would give, down to the formatting of the output and which inputs are type errors. Where it can't promise that, it says so.

Haskell scripting. rhci -r script.hs, or a #!/usr/bin/env -S rhci -r line at the top of the file, runs main with the arguments on getArgs, and -i turns a pure function into a filter over standard input. This is for the kind of script you'd otherwise write in a shell or Ruby, not for long-running or compute-heavy programs: rhci is an interpreter in Ruby, and on tight loops it's about 100 times slower than compiled GHC.

Portability and size. Each release is three files: a Cosmopolitan executable that runs unchanged on Linux, macOS, Windows and the BSDs, on x86-64 and ARM64, needing no libc; a static Linux binary for systems of any age; and a single Ruby file for anywhere Ruby is. None of them needs a toolchain, a package, or any file besides itself.

Haskell as an embedded scripting language. As a Ruby library, rhci gives a Ruby program (or anything that embeds Ruby) a Haskell interpreter with sessions that are isolated from each other and from the host, with limits on evaluation steps, stack depth, time and Integer size, and with file access that can be switched off.

  • Two runtimes, one source. The same Ruby code runs on CRuby and on mruby. The standalone binaries are mruby with rhci compiled in, and their evaluation loop runs in C.
  • A real compiler pipeline, kept small. About 23 000 lines including the Haskell Prelude.
  • Startup by snapshot. The compiled Prelude ships as a build-time snapshot. Only what a typical input needs is decoded at startup, the rest loads on demand, or in the background while you type.
  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)

The design is described in docs/ARCHITECTURE.md.

Each release, on GitHub or CodeFloe, has these files, all self-contained (the Prelude is embedded); copy one anywhere on your PATH:

File Runs on
rhci-1.0.0 Linux, macOS, FreeBSD, OpenBSD, NetBSD; x86-64 and ARM64. One Cosmopolitan executable, no libc needed. Verified on Linux x86-64 so far; reports from the other systems are welcome
rhci-1.0.0.exe Windows, x86-64 and ARM64: the same file, named so that Windows runs it
rhci-1.0.0-linux-x86_64-musl Any x86-64 Linux, however old: a static binary
rhci-1.0.0.rb Anything with a Ruby from 2.7 to 3.4 ( chmod +x it, orruby rhci-1.0.0.rb )

The only file rhci writes is its history, ~/.rhci_history.

Requirements: Ruby with rake (the build is tested with CRuby 3.4, pinned in mise.toml), git and a C compiler. The build fetches mruby 4.0.0 itself.

rake mruby      # build/rhci/bin/rhci: a binary for this machine's glibc
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 has the details, including how the release builds are made.

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
bash
$ 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

Strings print raw, other values print with show. The exit status is 0 on success, 1 for a Haskell exception, 2 for a usage error, 3 for a parse, scope or type error, and 4 for something unsupported.

Command Action
expression Evaluate and print it; IO actions are run. The result is it
x = ... ,f x = ... ,data ,class ,instance Define at the prompt
:{ ...:} A multi-line input
:type EXPR The expression's type ( :t works, as does any unambiguous prefix)
:kind TYPE The type's kind
:info NAME What a name is: its type, class, instances, fixity
:load FILE... /:reload Load modules (prompt definitions are discarded) / load them again
:quit Leave (or Ctrl-D)
:? ,:help List the commands

Ctrl-C interrupts an evaluation, including a blocked getLine.

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

Sessions are isolated from each other and from the host. Limits cover evaluation steps, stack depth, wall-clock time and the size of Integers, and files: false turns off file IO.

Haskell 2010 with type classes (single-parameter, including constructor classes), deriving for Show, Read, Eq, Ord, Enum and Bounded, records, modules with export and import lists, where/ let/guards/ case, list comprehensions, do notation (with MonadFail), bang patterns, kind inference with GHC2021's polymorphic kinds, and the GHCi prompt's rules: extended defaulting, no monomorphism restriction, it, redefinition.

Not supported, by choice and with an explicit error: GHC extensions other than bang patterns and polymorphic kinds (GADTs, type families, rank-n types, multi-parameter classes, existential types, ...), 32-bit Float, tuple sections, non-ASCII identifiers, default declarations, foreign imports, and packages other than the base and containers modules listed above.

Every answer rhci gives is meant to be GHCi's, so it's tested against the real GHC 9.6:

  • An oracle of about 1 700 hand-written probes with GHCi's recorded answers, run byrake on both runtimes.
  • Differential fuzzing: about twenty generators compare rhci with GHCi or runghc on random input: expressions with error values that expose evaluation order, whole multi-module programs, GHCi sessions, defaulting, numbers at the 64-bit edges, user data types and classes, damagedread input, deliberate type errors, lexical traps, laziness, kinds and the breadth of the libraries. Over 140 000 generated inputs and 2 800 programs agree, on CRuby and on mruby.
  • The exercism Haskell track: its reference solutions and test suites run on rhci and on GHC.

test/fuzz/README.md lists what each fuzzer covers and what it found (quite a lot, early on), and docs/DEVELOPMENT.md how the project is organised.

Designed by Leon Becker; implementation largely by Claude Code. The project's structure, scope, and design decisions are mine; most of the code itself was produced by Claude under my direction.

  • GHCi : the real thing, and the reference rhci is tested against. Use it for anything beyond quick checks and small scripts.
  • Hugs : the classic small Haskell interpreter, no longer maintained.
  • MicroHs : a small Haskell implementation compiling to combinators, with its own REPL.

rhci is MIT-licensed. See LICENSE.

Parts are derived from other projects, and their licences apply to those parts (details in THIRD_PARTY_NOTICES):

Component Used for License
GHC's base library prelude/Prelude.hs (ported) BSD-style
containers prelude/Data/Map ,prelude/Data/Set (ported) BSD-3-Clause
Unicode Character Database lib/rhci/unicode_data.rb (generated) Unicode License v3
mruby the standalone binaries (not in this repository) MIT
Cosmopolitan Libc the portable rhci binary ISC
musl the rhci-linux-x86_64-musl binary MIT
── more in #developer-tools 4 stories · sorted by recency
── more on @rhci 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/show-hn-rhci-a-small…] indexed:0 read:8min 2026-10-11 · —