Publish a post about Valeri+org-babel

This commit is contained in:
Konstantin Nazarov 2024-10-12 21:28:06 +01:00
parent 30f6f5e030
commit 790a749cf3
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22

View file

@ -0,0 +1,76 @@
X-Date: 2024-10-12T20:12:24Z
X-Note-Id: 087d89cd-27ec-43d4-9d46-b48eed3ce301
Subject: Valeri notebook programming with Org Babel
X-Slug: valeri_notebook_programming_with_org_babel
I really wanted to get an interactive programming environment for [Valeri](https://git.knazarov.com/knazarov/valeri)
which works in a similar fashion to [Jupyter notebooks](https://jupyter.org/). It is of course possible to add the
support for the Valeri kernel to Jupyter itself, but it's a lot of work. So I opted for
[Org Babel](https://orgmode.org/worg/org-contrib/babel/intro.html), which is an extension to Emacs
[org-mode](https://orgmode.org/) allowing the execution of code snippets within the Org markup code blocks.
The [implementation](https://git.knazarov.com/knazarov/valeri/src/branch/master/emacs) turned out to be quite
simple: in just a few hundred lines of code, I've got support for running the Valeri REPL as a background
"inferior" process in Emacs. The rest just involved writing some glue code to add a bridge from Babel to
send the code body to the REPL and getting the text results back. Most of the code handling the interation
with the inferior REPL is already in Emacs base, so you don't have to write it on your own.
If you exclude all the glue code, this is what the implementation boils down to:
```
(defun org-babel-valeri-evaluate (buffer body)
"Pass BODY to the Valeri process in BUFFER."
(let ((escaped-body (format "\x1b[200~%s\x1b[201~" body))
(eoe-string (format "\n(println \"%s\")\n" valeri-eoe-indicator)))
(mapconcat
#'identity
(butlast
(split-string
(mapconcat
#'org-trim
(org-babel-comint-with-output
(buffer valeri-eoe-indicator t escaped-body)
(insert (org-babel-chomp escaped-body) eoe-string)
(comint-send-input nil t))
"\n") "[\r\n]")) "\n")
)
)
```
It just takes the `body` string, wraps it into the [bracketed paste](https://en.wikipedia.org/wiki/Bracketed-paste)
escape sequence, sends to the REPL buffer, reads the result back and trims unnecessary symbols.
The usage of this integration is quite simple as well. You just create a file with `.org` extension that can look
like this:
```
* Notebook programming with Valeri
The following code block will create a new persistent
interpreter session and load the factorial function
there.
#+begin_src valeri :session val
(fn fact (n)
(if (<= n 0)
1
(* n (fact (- n 1)))))
#+end_src
#+RESULTS:
: #<function fact>
And this code block will run the factorial function
and output the result.
#+begin_src valeri :session val
(println "12! is" (fact 12))
#+end_src
#+RESULTS:
: 12! is 479001600
```
If you position the cursor in the `begin_src/end_src` block and press Ctrl-C Ctrl-C, it will be evaluated
and the `#+RESULTS` will be updated. What is also convenient is that multiple code blocks can share a
session, so you can define a function in the first block and call it in the subsequent block.