Stream: ideas

Topic: Jupyter kernel for Roc


view this post on Zulip Aurélien Geron (Jul 02 2026 at 07:38):

I've started implementing a Jupyter kernel for Roc. I've got a first version running. It's not quite ready for prime-time yet, but here's a teaser:

roc-kernel.mov

view this post on Zulip Jonathan (Jul 02 2026 at 08:19):

How does jupyter work with functional langs and immutability? I'm not sure how to phrase the question other than by example - the notebooks I use with Elixir must execute in sequence, and rerunning a cell only captures variable state from earlier cells and not any that have been reassigned. This prevents the number of times, or sequence you have run cells, from being a concern except for external effects. Can you control this kind of behaviour with a jupyter kernel (if desirable)? I imagine it might be interesting with Roc with vars too, but lack of shadowing does prevent the badness I'm thinking of.

view this post on Zulip Aurélien Geron (Jul 02 2026 at 09:00):

Basically a Jupyter kernel is a little server that communicates over 5 ZeroMQ sockets:

I've plugged into the Roc REPL so it's exactly like a regular Roc REPL session. In the REPL, you can redefine a function (which is what I demo near the end of the video by redefining the square function to return xxx. I don't know how the REPL does that.

view this post on Zulip Aurélien Geron (Jul 02 2026 at 09:32):

For example, when I ran square(3), the code came in through the Shell socket. The kernel immediately broadcast status (busy) over the IOPub socket. The Roc compiler evaluated the code, and the kernel pushed the "9" out over IOPub. Finally, the kernel told IOPub to broadcast status (idle) and told Shell that the request was complete. Meanwhile, Heartbeat was pinging in the background to keep the connection alive.

view this post on Zulip Aurélien Geron (Jul 02 2026 at 10:08):

There are various ways to build a Jupyter kernel. Here are a few options:

image.png

JMP means Jupyter Messaging Protocol.

Option A is the simplest: the kernel just launches a roc repl and uses stdin/stdout to send commands to it, and capture the responses. It's cool for a proof of concept, but it's brittle (any change to the REPL's UI will break it) and it doesn't allow the full set of Jupyter features (e.g., no autocomplete).

Option B would improve greatly on option A: instead of capturing the REPL's stdin/stdout, Roc could have a special mode which would give access to all of its REPL features through a programmatic interface, such as JSON RPC. The kernel would launch something like roc eval-server and communicate with it. Any tool can query it using JSON RPC.

Option C is the same as Option B but it uses the JMP protocol instead of JSON RPC. This means that the roc binary integrates the Jupyter kernel directly: you run roc jupyter and it acts as a Roc kernel, serving the 5 ZMQ sockets I listed earlier. Any JMP-compatible tool can use it.

Option D: the kernel lives separately from the main code, but it is based on the same Zig libraries. This is what I have implemented to get started, but the downside is that the kernel needs to be recompiled every time the Roc compiler changes. Moreover, it makes the kernel quite large since it includes Roc.

Wdyt?

view this post on Zulip Jonathan (Jul 02 2026 at 21:32):

Oh I couldn't say :smile: but thank you I now understand Jupyter better!

view this post on Zulip Aurélien Geron (Jul 03 2026 at 06:31):

I implemented option B in draft PR #9923: running roc repl --rpc will make the REPL wait for JSON commands and output JSON outputs, for example:

% roc repl --rpc
{"method": "repl.start", "id": "req001"}
{"session_id":"68da396c-9302-4d2a-a800-a3b644665c68","id":"req001"}
{"method": "repl.evaluate", "params": {"session_id": "68da396c-9302-4d2a-a800-a3b644665c68", "code": "2 + 2"}, "id": "req003"}
{"result":"4.0","stdout":"","diagnostics":null,"id":"req003"}
{"method": "repl.stop", "params": {"session_id": "68da396c-9302-4d2a-a800-a3b644665c68"}, "id": "req004"}
{"success":true,"id":"req004"}

I built a Roc kernel for Jupyter based on this RPC mode. Feel free to try it out, you should have an experience similar to the video I posted earlier in this thread. You need to compile Roc with the draft PR #9923, and follow the build & install instructions for the Roc kernel.

view this post on Zulip Aurélien Geron (Jul 03 2026 at 06:33):

@Richard Feldman said that he'd rather not introduce JSON into any of our CLI or compiler outputs. I'm happy to change the draft PR: what other options do we have to ensure that the REPL can be controlled programmatically?

I went this JSON-RPC route because I believe that's how the LSP works? Actually, perhaps we could just extend the LSP to support these extra methods (start/stop/reset a session and evaluate some code in a given session)?

view this post on Zulip Luke Boswell (Jul 03 2026 at 08:34):

We could add support for a repl "plugin" script like roc glue maybe? and then handle the json inside that?

view this post on Zulip Luke Boswell (Jul 03 2026 at 08:35):

Or maybe this is something roc tooling could help with?

view this post on Zulip Luke Boswell (Jul 03 2026 at 08:35):

Idk ... just throwing some ideas around

view this post on Zulip Aurélien Geron (Jul 03 2026 at 08:44):

I'm not familiar with roc glue or roc tooling. I believe that roc experimental-lsp starts a JSON RPC server (JSON objects in through stdin, JSON objects out through stdout), so I figured that this was the way to go, since it's very similar functionality (i.e., the LSP provides static info & features, while the REPL-RPC provides dynamic features).

view this post on Zulip Richard Feldman (Jul 03 2026 at 14:41):

yeah I don't want us to

view this post on Zulip Richard Feldman (Jul 03 2026 at 14:41):

have lsp in the CLI either :sweat_smile:

view this post on Zulip Richard Feldman (Jul 03 2026 at 14:41):

in general I want to follow glue's example

view this post on Zulip Richard Feldman (Jul 03 2026 at 14:41):

where you write roc code to transform into whatever format you need (json etc)

view this post on Zulip Norbert Hajagos (Jul 03 2026 at 22:04):

Luke Boswell said:

We could add support for a repl "plugin" script like roc glue maybe? and then handle the json inside that?

I'm also not familiar with how glue works, but what comes to mind is a built in platform that is coupled to the compiler. You could only use it if you version it correctly in your app, but other than that, it would give you programatic access to the repl.
As I'm typing this, the question arises: how do we do IO in such an application? And that is a rabit hole where the compiler includes a builtin platform with every possible io type, so I immediately dislike my idea.

How would the plugin approach work?

view this post on Zulip Richard Feldman (Jul 03 2026 at 22:07):

for something like this I assume we wouldn't support arbitrary I/O

view this post on Zulip Richard Feldman (Jul 03 2026 at 22:07):

glue is like "give me a list of output files you want, and their contents, and I will write them to disk" and I think it has restrictions on where they can go (e.g. must be a subdirectory of the current dir or something)

view this post on Zulip Luke Boswell (Jul 04 2026 at 01:27):

Yes roc glue is what I have been calling the "plugin" use-case... there are no prebuilt host files in the platform, the roc cli itself embeds the roc compiler (it's already there for roc build etc) and compiles the app and runs the script.

This is a pattern I have used elsewhere too, like another totally unrelated program that runs the roc apps instead of the roc cli building standalone binaries.

view this post on Zulip Luke Boswell (Jul 04 2026 at 01:29):

In future you could imagine libroc for doing this more easily, but for now you can wrap the roc modules using a zig program easy by pointing it at the roc-lang/roc repo.

view this post on Zulip Luke Boswell (Jul 04 2026 at 01:30):

The LSP is currently named experimental, because the long term goal is to remove all that logic from the compiler, and decouple that. So the LSP can upgrade independently etc.

view this post on Zulip Aurélien Geron (Jul 04 2026 at 16:19):

I'm happy to follow the Roc glue path, but I'm not sure I have a good understanding of the architecture we're shooting for. Is it the option D in my diagram above? Basically the Roc eval tools would be compiled directly into the Roc kernel?

view this post on Zulip Aurélien Geron (Jul 06 2026 at 07:57):

I've already implemented option D, where the Roc kernel simply uses the Roc compiler code to evaluate the cells directly, without using the Roc CLI at all. It works fine, but it has two drawbacks:

  1. Every time the Roc compiler changes, we must build and deploy the new Roc kernel. Jupyter kernels for other languages typically don't change much since they just proxy code from Jupyter to the target language's interpreter or compiler. For the end user, bumping the language version just requires updating one thing, not two.
  2. The Roc kernel is quite large for a Jupyter kernel: 200 MB. Since most kernels are just simple proxies, they are typically quite small.

I still haven't really understood the alternative "roc-glue-like" architecture you have in mind. Would someone be kind enough to draw a picture? It sounds to me like it's similar to Option D, so it would still have these two issues, wouldn't it? I'm not pushing back, I'm just confused.

view this post on Zulip Jonathan (Jul 06 2026 at 09:25):

I am also not entirely sure, thinking out loud for what it's worth? I don't know how 'glue like' it is, but the repl could operate like the elm architecture, where a plugin receives/subscribes to repl events, and emits actions (or directly calls effectful host functions not sure) etc, like "evaluate snippet" or "write these bytes to a socket". The compiler supplies the repl platform, and expects the plugin to expose handlers etc. I think you would be able to supply this to Roc like roc repl --plugin jupyter.roc, and the terminal repl could be a bundled terminal.roc plugin?

view this post on Zulip Jonathan (Jul 06 2026 at 09:44):

I think this is along the lines of what Luke meant - the repl plugin is compiled against a platform, but stops short of linking against it and instead roc drives it directly? This is also my very rough understanding of the glue pipeline.

view this post on Zulip Luke Boswell (Jul 06 2026 at 09:53):

There's definitely some design questions here about what are the effects or the API for this kind of functionality

view this post on Zulip Richard Feldman (Jul 06 2026 at 12:58):

Aurélien Geron said:

I still haven't really understood the alternative "roc-glue-like" architecture you have in mind. Would someone be kind enough to draw a picture? It sounds to me like it's similar to Option D, so it would still have these two issues, wouldn't it? I'm not pushing back, I'm just confused.

yeah sorry, I haven't really had time to dig deeply into this...the basic idea is:

view this post on Zulip Richard Feldman (Jul 06 2026 at 12:59):

so if you wanted to go the JSON route, you could implement that function to take the Foo and Bar structs from the compiler and just have the Roc function serialize them as JSON and return that, at which point you have the same interface to Jupyter as the original PR (but with the indirection of having the Roc code say "I want to turn this into JSON" instead of having that be hardcoded in the compiler)

view this post on Zulip Richard Feldman (Jul 06 2026 at 13:00):

but it also opens up other possibilities, e.g. you could make the Roc code not be restricted to just returning a Str that gets printed to stdout, you could do things like have it describe what bytes to send over a socket, and the CLI could take care of opening that socket for you, watching the filesystem, and just calling the Roc function repeatedly whenever things change

view this post on Zulip Richard Feldman (Jul 06 2026 at 13:01):

does that make sense?

view this post on Zulip Aurélien Geron (Jul 06 2026 at 14:52):

Ah, that makes complete sense, thanks for the detailed explanation @Richard Feldman . :folded_hands:
So the Jupyter kernel could remain exactly the same as today, but instead of calling roc repl --rpc, it would call something like roc repl json-rpc.roc.

Would the json-rpc.roc script be based on the CLI platform to read from stdin and write to stdout? If so, how would it interface with Roc's REPL (for the Foo and Bar data structures)? Or should I develop a Roc platform designed for this use-case, which would provide both Stdin/Stdout and the REPL interface?

view this post on Zulip Richard Feldman (Jul 06 2026 at 14:53):

is it literally just taking in repl inputs and responding with output?

view this post on Zulip Richard Feldman (Jul 06 2026 at 14:53):

if so, we could abstract it more than that - like it just receives a Str from the repl and then returns a Str for what to output, for example

view this post on Zulip Aurélien Geron (Jul 06 2026 at 15:02):

Yes, the core functionality is just Str in, Str out, but kernels typically offer a few more functionalities, including auto-complete and type info.

view this post on Zulip Aurélien Geron (Jul 06 2026 at 15:08):

Oh and there can also be rich outputs, like HTML, images, and even interactive outputs (HTML+JavaScript).

view this post on Zulip Richard Feldman (Jul 06 2026 at 15:53):

I'd say let's start with just the core functionality then

view this post on Zulip Richard Feldman (Jul 06 2026 at 15:53):

like basically a way to drive the repl programmatically


Last updated: Jul 23 2026 at 13:15 UTC