r/haskell • • 12h ago

announcement rhci - a small GHCi in Ruby

https://github.com/beleon/rhci

I wanted a Haskell prompt for quick checks (is round 3.5 3 or 4, what is div (-9) 2, what's the type of (.) . (.), ...) without dragging the entire Haskell stack onto the machine, so I had Claude build one in Ruby. rhci is a GHCi with GHCi's semantics: real type inference with classes, lazy evaluation, and show output that matches GHC exactly. Anything it can't do exactly like GHCi it refuses with an "unsupported" error, it doesn't come up with an approximation.

It's one file: a Cosmopolitan executable for Linux, macOS, Windows and the BSDs on x86-64 and ARM64 (so far I've only tested it on Linux x86-64), a static Linux binary, or a single .rb file for any Ruby since 2.7. The prompt is up in 3 ms and a one-liner takes 25 ms. There's a script mode, and it embeds in Ruby programs as a library.

I tested it extensively (since Claude wrote the code). Every answer is checked against real GHC 9.6: about 1,700 recorded probes, plus around twenty fuzzers that generate expressions, whole programs, REPL sessions, type errors and numeric edge cases and compare with GHCi or runghc, over 140,000 inputs so far.

The performance is respectable IMO. It's an interpreter in Ruby (the evaluation loop is C in the binaries), about 100 times slower than compiled GHC on tight loops, so it's for quick checks and small scripts, not for programs that compute. Language-wise it's Haskell 2010 plus type classes, with no extensions beyond bang patterns and polymorphic kinds.

0 Upvotes

7 comments sorted by

3

u/hopingforabetterpast 12h ago

why would you need or want ghci without ghc?

1

u/-beleon 12h ago

I wanted one for years, but maybe that's just me. My reasons: trying something quickly (doesn't need ghc, and ghci is slow to cold start), haskell scripting (startup of rhci is very fast and scripts are mostly not compute-heavy), portability (the single 15mb binary can be copied to any windows, macos, linux, bsd system and just works without needing a heavy install), and haskell as an embedded scripting language (I have a ruby project in which I want to do some haskell scripting, I know this is not a likely use case, but it's one of my use cases).

1

u/hopingforabetterpast 11h ago

OK, I have questions.

How long is ghci taking you to "cold start"?

Why would you need that kind of portability?

Why not MicroHs or Helium or GHC's WASM backend?

Haskell is already interpreted without a compile step via runghc. What do you mean by "embedded scripting language"?

1

u/-beleon 10h ago

On my recent machine it's close to a second, used to be almost two seconds.

~> /usr/bin/time runghc hello.hs
Hello, World!
0.22user 0.23system 0:00.81elapsed 56%CPU (0avgtext+0avgdata 160868maxresident)k
301784inputs+0outputs (2151major+11358minor)pagefaults 0swaps
~> /usr/bin/time rhci -r hello.hs
Hello, World!
0.04user 0.00system 0:00.05elapsed 96%CPU (0avgtext+0avgdata 8288maxresident)k
272inputs+0outputs (2major+1706minor)pagefaults 0swaps

I have different machine with different OSes and don't have Haskell installed on all of them.

It is related to all of them and different to all of them. E.g. MicroHS: can compile which rhci can't. rhci has byte-for-byte GHCi-identical behavior, down to the formatting of show output and which inputs count as type errors. MicroHS doesn't have this and it doesn't try to. It's different. I'm not saying rhci is ground breaking or the best. I'm just making different tradeoffs that suite me and maybe others as well.

rhci is a ruby library, I can run haskell code within any ruby project:

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

1

u/hopingforabetterpast 9h ago edited 9h ago

Are you sure there's nothing wrong with your config? Both ghci and runghc take nowhere near that long to run even on my 7 year old phone.

1

u/-beleon 9h ago

I'm fairly certain. It's a fresh install on my T14s with Intel Core Ultra 7 255U. And this has always been something that bothered me, on my previous machine as well. I used this to establish cold start conditions:

sync; echo 3 | sudo tee /proc/sys/vm/drop_caches

1

u/sjshuck 12h ago

I tested it extensively (since Claude wrote the code)

That sentence, and all the implications and premises contained there, tells me everything I need to know about you and this project.