{"slug": "show-hn-rhci-a-small-ghci-in-ruby", "title": "Show HN: Rhci – A Small GHCi in Ruby", "summary": "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.", "body_md": "**A small GHCi in Ruby: a Haskell REPL with GHCi's semantics.**\n\n  One binary, or one Ruby file. Prompt in 3 ms, a one-liner in 25 ms.\n\n  Source and releases: [GitHub](https://github.com/beleon/rhci) /\n  [CodeFloe](https://codefloe.com/beleon/rhci)\n\n``` bash\n$ rhci\nrhci> xs = [x * x | x <- [1 .. 10], odd x]\nrhci> sum xs\n165\nrhci> :t foldr\nfoldr :: Foldable c => (a -> b -> b) -> b -> c a -> b\nrhci> import qualified Data.Map as M\nrhci> M.fromListWith (+) [(c, 1) | c <- \"mississippi\"]\nfromList [('i',4),('m',1),('p',2),('s',4)]\nrhci> show (0.1 + 0.2)\n\"0.30000000000000004\"\nrhci> [1, 2, undefined]\n[1,2,*** Exception: Prelude.undefined\n```\n\n- **GHCi's semantics:** Hindley-Milner type inference with type classes\n(dictionary passing, constructor classes, defaulting as GHCi does it),\nlazy evaluation with sharing, and`show` output that matches GHC\nexactly, from`Double` formatting to`Int` wrap-around\n- **Fails rather than guesses:** anything rhci can't do exactly as GHCi\nwould is refused with an explicit`unsupported:` error\n- **A real Prelude:** ported from GHC's base, plus Data.List, Data.Char,\nData.Maybe, Control.Monad, Data.Map, Data.Set, Data.IORef, Data.Bits,\nData.Ratio, Numeric, Text.Printf, Text.Read and file IO\n- **The GHCi workflow:**`:type` ,`:kind` ,`:info` ,`:load` /`:reload` of\nmulti-module programs,`:{ ... :}` blocks,`it` , imports at the prompt, a\nline editor with history and Tab completion, Ctrl-C to interrupt\n- **Scripts and one-liners:**`rhci -c EXPR` evaluates and exits,`-i` applies the result to standard input like`interact` , and`-r FILE` runs a file's`main` like`runghc` , so a`#!` line makes a Haskell file\nexecutable\n- **Small and self-contained:** one portable executable with no\ndependencies at all (15 MB, built for Linux, macOS, Windows and the\nBSDs), a 7 MB static Linux binary, or a single Ruby file for any CRuby\nfrom 2.7 to 3.4; as a library it embeds in Ruby programs with limits on\nevaluation steps, stack depth, time and Integer size\n\n**Trying something quickly.** What does `words` do with tabs, does this\nfold terminate, what's the type of that composition. GHCi answers all of\nthat, but it comes with GHC, which is gigabytes to install and a\nnoticeable wait before the prompt, and it isn't there on the machine you\njust `ssh` ed into. rhci is one file you copy, with a prompt in\nmilliseconds, and the answer is the one GHCi would give, down to the\nformatting of the output and which inputs are type errors. Where it\ncan't promise that, it says so.\n\n**Haskell scripting.** `rhci -r script.hs`, or a `#!/usr/bin/env -S rhci -r`\nline at the top of the file, runs `main` with the arguments on `getArgs`,\nand `-i` turns a pure function into a filter over standard input. This is\nfor the kind of script you'd otherwise write in a shell or Ruby, not for\nlong-running or compute-heavy programs: rhci is an interpreter in Ruby,\nand on tight loops it's about 100 times slower than compiled GHC.\n\n**Portability and size.** Each release is three files: a Cosmopolitan\nexecutable that runs unchanged on Linux, macOS, Windows and the BSDs,\non x86-64 and ARM64, needing no libc; a static Linux binary for systems\nof any age; and a single Ruby file for anywhere Ruby is. None of them\nneeds a toolchain, a package, or any file besides itself.\n\n**Haskell as an embedded scripting language.** As a Ruby library, rhci\ngives a Ruby program (or anything that embeds Ruby) a Haskell interpreter\nwith sessions that are isolated from each other and from the host, with\nlimits on evaluation steps, stack depth, time and Integer size, and with\nfile access that can be switched off.\n\n- **Two runtimes, one source.** The same Ruby code runs on CRuby and on\nmruby. The standalone binaries are mruby with rhci compiled in, and\ntheir evaluation loop runs in C.\n- **A real compiler pipeline, kept small.** About 23 000 lines including\nthe Haskell Prelude.\n- **Startup by snapshot.** The compiled Prelude ships as a build-time\nsnapshot. Only what a typical input needs is decoded at startup, the\nrest loads on demand, or in the background while you type.\n\n``` php\n  input --> lexer + layout --> parser --> renamer --> type checker --> desugar to Core\n                                                     (classes, kinds,     |\n                                                      defaulting)         v\n                    output <-- lazy graph-reduction machine <-- simplifier + lowering\n                               (Ruby, or C in the binaries)\n```\n\nThe design is described in [docs/ARCHITECTURE.md](https://github.com/beleon/rhci/blob/main/docs/ARCHITECTURE.md).\n\nEach release, on [GitHub](https://github.com/beleon/rhci/releases) or\n[CodeFloe](https://codefloe.com/beleon/rhci/releases), has these files,\nall self-contained (the Prelude is embedded); copy one anywhere on your\n`PATH`:\n\n| File | Runs on | \n|---|---|\n| `rhci-1.0.0` | Linux, macOS, FreeBSD, OpenBSD, NetBSD; x86-64 and ARM64. One [Cosmopolitan](https://github.com/jart/cosmopolitan) executable, no libc needed. Verified on Linux x86-64 so far; reports from the other systems are welcome | \n| `rhci-1.0.0.exe` | Windows, x86-64 and ARM64: the same file, named so that Windows runs it | \n| `rhci-1.0.0-linux-x86_64-musl` | Any x86-64 Linux, however old: a static binary | \n| `rhci-1.0.0.rb` | Anything with a Ruby from 2.7 to 3.4 ( `chmod +x` it, or`ruby rhci-1.0.0.rb` ) | \n\nThe only file rhci writes is its history, `~/.rhci_history`.\n\nRequirements: Ruby with rake (the build is tested with CRuby 3.4, pinned\nin `mise.toml`), git and a C compiler. The build fetches mruby 4.0.0\nitself.\n\n```\nrake mruby      # build/rhci/bin/rhci: a binary for this machine's glibc\n                # build/rhci.rb: the compact CRuby build (also: rake compact)\nrake            # lint, build, the tests on CRuby and mruby (rake bench: startup budgets)\nrake release    # the release files into dist/, with SHA256SUMS (needs curl, unzip, podman)\n```\n\n[docs/DEVELOPMENT.md](https://github.com/beleon/rhci/blob/main/docs/DEVELOPMENT.md) has the details, including\nhow the release builds are made.\n\n```\nrhci [FILE.hs...]                 interactive session, FILEs loaded\nrhci [FILE.hs...] [-i] -c EXPR [ARG...]   apply pure EXPR to ARGs (Haskell expressions)\nrhci [FILE.hs...] [-i] -x EXPR [ARG...]   run IO action EXPR; ARGs via getArgs\nrhci -r FILE.hs [ARG...]          run FILE's main with ARGs, like runghc\nrhci -h | --help | --version\nbash\n$ rhci -c 'sum [1..100]'\n5050\n$ rhci -c 'map toUpper' '\"hello\"'\nHELLO\n$ printf 'one\\ntwo\\n' | rhci -i -c 'unlines . map reverse . lines'\neno\nowt\n$ rhci -x 'getArgs >>= print' a b\n[\"a\",\"b\"]\n$ cat hello.hs\n#!/usr/bin/env -S rhci -r\nimport System.Environment\nmain :: IO ()\nmain = getArgs >>= putStrLn . unwords . (\"hello\" :)\n$ ./hello.hs world\nhello world\n```\n\nStrings print raw, other values print with `show`. The exit status is 0\non success, 1 for a Haskell exception, 2 for a usage error, 3 for a\nparse, scope or type error, and 4 for something unsupported.\n\n| Command | Action | \n|---|---|\n| `expression` | Evaluate and print it; IO actions are run. The result is `it` | \n| `x = ...` ,`f x = ...` ,`data` ,`class` ,`instance` | Define at the prompt | \n| `:{` ...`:}` | A multi-line input | \n| `:type EXPR` | The expression's type ( `:t` works, as does any unambiguous prefix) | \n| `:kind TYPE` | The type's kind | \n| `:info NAME` | What a name is: its type, class, instances, fixity | \n| `:load FILE...` /`:reload` | Load modules (prompt definitions are discarded) / load them again | \n| `:quit` | Leave (or Ctrl-D) | \n| `:?` ,`:help` | List the commands | \n\nCtrl-C interrupts an evaluation, including a blocked `getLine`.\n\n```\nrequire_relative 'rhci'   # build/rhci.rb\nrequire 'stringio'\n\nout = StringIO.new\nsession = Rhci::Session.new(stdout: out, limits: { steps: 10_000_000 })\n\nsession.eval('xs = [x * x | x <- [1 .. 10]]')\nsession.eval('sum xs').status        # => :ok, and out.string is \"385\\n\"\nsession.eval('head []').message      # => \"Prelude.head: empty list\"\nsession.eval('length [1 ..]').status # => :limit\n```\n\nSessions are isolated from each other and from the host. Limits cover\nevaluation steps, stack depth, wall-clock time and the size of Integers,\nand `files: false` turns off file IO.\n\nHaskell 2010 with type classes (single-parameter, including constructor\nclasses), `deriving` for Show, Read, Eq, Ord, Enum and Bounded, records,\nmodules with export and import lists, `where`/` let`/guards/` case`, list\ncomprehensions, `do` notation (with MonadFail), bang patterns, kind\ninference with GHC2021's polymorphic kinds, and the GHCi prompt's rules:\nextended defaulting, no monomorphism restriction, `it`, redefinition.\n\nNot supported, by choice and with an explicit error: GHC extensions other\nthan bang patterns and polymorphic kinds (GADTs, type families, rank-n\ntypes, multi-parameter classes, existential types, ...), 32-bit `Float`,\ntuple sections, non-ASCII identifiers, `default` declarations, foreign\nimports, and packages other than the base and containers modules listed\nabove.\n\nEvery answer rhci gives is meant to be GHCi's, so it's tested against the real GHC 9.6:\n\n- **An oracle** of about 1 700 hand-written probes with GHCi's recorded\nanswers, run by`rake` on both runtimes.\n- **Differential fuzzing:** about twenty generators compare rhci with GHCi\nor runghc on random input: expressions with error values that expose\nevaluation order, whole multi-module programs, GHCi sessions,\ndefaulting, numbers at the 64-bit edges, user data types and classes,\ndamaged`read` input, deliberate type errors, lexical traps, laziness,\nkinds and the breadth of the libraries. Over 140 000 generated inputs\nand 2 800 programs agree, on CRuby and on mruby.\n- **The exercism Haskell track:** its reference solutions and test suites\nrun on rhci and on GHC.\n\n[test/fuzz/README.md](https://github.com/beleon/rhci/blob/main/test/fuzz/README.md) lists what each fuzzer covers\nand what it found (quite a lot, early on), and\n[docs/DEVELOPMENT.md](https://github.com/beleon/rhci/blob/main/docs/DEVELOPMENT.md) how the project is organised.\n\nDesigned by [Leon Becker](https://blog.leonbecker.de); implementation\nlargely by [Claude Code](https://claude.com/claude-code). The project's\nstructure, scope, and design decisions are mine; most of the code itself\nwas produced by Claude under my direction.\n\n- [**GHCi**](https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html) :\nthe real thing, and the reference rhci is tested against. Use it for\nanything beyond quick checks and small scripts.\n- [**Hugs**](https://www.haskell.org/hugs/) : the classic small Haskell\ninterpreter, no longer maintained.\n- [**MicroHs**](https://github.com/augustss/MicroHs) : a small Haskell\nimplementation compiling to combinators, with its own REPL.\n\nrhci is MIT-licensed. See [LICENSE](https://github.com/beleon/rhci/blob/main/LICENSE).\n\nParts are derived from other projects, and their licences apply to those\nparts (details in [THIRD_PARTY_NOTICES](https://github.com/beleon/rhci/blob/main/THIRD_PARTY_NOTICES)):\n\n| Component | Used for | License | \n|---|---|---|\n| GHC's base library | `prelude/Prelude.hs` (ported) | BSD-style | \n| containers | `prelude/Data/Map` ,`prelude/Data/Set` (ported) | BSD-3-Clause | \n| Unicode Character Database | `lib/rhci/unicode_data.rb` (generated) | Unicode License v3 | \n| mruby | the standalone binaries (not in this repository) | MIT | \n| Cosmopolitan Libc | the portable `rhci` binary | ISC | \n| musl | the `rhci-linux-x86_64-musl` binary | MIT |", "url": "https://wpnews.pro/news/show-hn-rhci-a-small-ghci-in-ruby", "canonical_source": "https://github.com/beleon/rhci", "published_at": "2026-10-11 21:32:18+00:00", "updated_at": "2026-10-11 22:04:07.025171+00:00", "lang": "en", "topics": ["developer-tools"], "entities": ["rhci", "Ruby", "GHCi", "GHC", "beleon", "Cosmopolitan", "CRuby", "mruby"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/show-hn-rhci-a-small-ghci-in-ruby", "markdown": "https://wpnews.pro/news/show-hn-rhci-a-small-ghci-in-ruby.md", "text": "https://wpnews.pro/news/show-hn-rhci-a-small-ghci-in-ruby.txt", "jsonld": "https://wpnews.pro/news/show-hn-rhci-a-small-ghci-in-ruby.jsonld"}}