This content originally appeared on DEV Community and was authored by Laura Viglioni
If you only knew the power of the Dark Side!
Tools like Jupyter Notebook are very well known and useful, although limited to a few languages. Wouldn't it be amazing to have this power to create notebooks with any other language?
In this text, we will focus on doing it with Haskell, although it is virtually possible to be done using any language.
Emacs has a very powerful mode called org-mode, I once wrote a text about using it to write presentations with beamer. This same mode allows us to write code snippets (and execute them!), which is helpful to write notes/documents/presentations and export them to several formats like pdf, HTML, markdown, LaTeX and more!
Pre-requisites
Your Emacs will need some packages: org
, org-babel
and haskell-mode
. If you use spacemacs it is enough to add these layers in your .spacemacs
:
(
;; ...
dotspacemacs-configuration-layers
'( org
haskell
;; ...
Of course, you must have GHC on your machine.
Improving what we already have
It is important to note that once you have those packages installed, Emacs already knows how to execute Haskell blocks. The motivation of this text is to compile the learning I had these last few months of how to do it better.
To run a code block is as simple as:
#+begin_src <language name>
<code>
#+end_src
and hit C-c C-c
. If your Emacs knows how to compile it, it will execute the code and put the result below your code.
Writing multiline Haskell code
The default way that org-babel
compiles your code is using GHCi
, so if you have to write a multiline code, then you need to do it as if we were inside a GHCi
buffer:
:{
-- a very verbose way to sum a sequence of numbers:
sumInts :: Int -> Int -> Int
sumInts a b =
if a == b
then b
else (+ a) $ (sumInts (a + 1) b)
:}
map (\[a,b] -> sumInts a b) [[0, 1] , [1, 3], [1,5], [2,10]]
Prelude> [1,6,15,54]
i.e. we need to put the multiline part of the code inside :{ :}
and what we want to be on the output on the last line. Also, it is important to note that, since it is running inside a GHCi
, we will only see the result of the last call.
We can use the GHCi
commands like :set -XDataKinds
too :))
You may be asking yourself what is that :exports both
. As I said earlier, we can export this org
file to several formats. The :exports
tag defines if we want to export the code, result, both or none. You can check out the other tags here.
Fun fact: GitHub understands org
files without any manual export. You can use org
files to READMEs, or even to post your notebooks.
Formatting the output
As you may have noticed in the excerpt above, the output has a Prelude>
"prefix", and it might get bigger if you import other libs or executes multiline blocks:
import Control.Monad
:{
map
(\x -> x*x + x + 1)
[1..10]
:}
Prelude Control.Monad| Prelude Control.Monad| Prelude Control.Monad| Prelude Control.Monad| [3,7,13,21,31,43,57,73,91,111]
We can avoid that with the :post
tag. This tag executes a function, of your choice, with the output of your code block as input. To get that, we will use... Yes, another code block :D
At the beginning of your code, add these lines:
#+name: org-babel-haskell-formatter
#+begin_src emacs-lisp :var strr="" :exports code
(format "%s"
(replace-regexp-in-string
(rx line-start
(+ (| alphanumeric blank "." "|" ">")))
"" (format "%s" strr)))
#+end_src
This is the file I use to store this func in my repo
For now on, on your Haskell code blocks, you add the #+name:
you gave to that code block:
:{
map
(\x -> x*x + x + 1)
[1..10]
:}
[3,7,13,21,31,43,57,73,91,111]
You might be asking yourself right now:
Will you always have to write this template on the #+begin_src
?
Unfortunately, yes. My recommendation is to create a snippet to generate this Haskell block code or to create some helper function that does that for you.
Will you have to define the formatter function on every org file?
No! :D
You can add to your config files an org file with that function definition and import it on your Emacs initiation using the org-babel-lob-ingest
function:
(with-eval-after-load "org"
;; load extra configs to org mode
(org-babel-lob-ingest "~/path/to/org-config-file.org"))
Note that if you add a relative path (./org-config-file.org
) it might fail.
A wild awesome feature appears!
One of the coolest stuff about using org
to write code snippets, even if you will not execute them, is that you can use specific modes while writing your snippet!
With the cursor inside the #+begin_src
block, call a function org-edit-special
(, '
on spacemacs default binding), then Emacs will open a new buffer with your language mode. To exit it, hit C-c '
.
Using external Haskell libs with Stack
This one was the trickiest to me, mostly because I'm not very familiar with the stack ecosystem. Maybe this is not the best way of doing it, but this is the way that I achieved it.
Stack
has a global project by default, you can check it out on ~/.stack/global-project
. Inside this directory, create a new project:
$ stack new org-haskell new-template
On ~/.stack/global-project/stack.yaml
add the following:
packages:
- org-haskell
After that, all the libs you have imported on your .stack/global-project/org-haskell/packages.yaml
will be available on stack ghci
. For org-babel
to use it instead of regular GHCi
, set this variable on your configs:
(setq haskell-process-type 'stack-ghci)
Know the power of the Dark Side!
I do hope this is helpful for you.
org-mode
is a very powerful tool, I do recommend that you know it and use it!
Stay safe, use masks (even if you already got your shots!) and use Emacs
xoxo
This content originally appeared on DEV Community and was authored by Laura Viglioni
Laura Viglioni | Sciencx (2021-09-05T03:30:51+00:00) Creating Haskell notebooks with org-mode. Retrieved from https://www.scien.cx/2021/09/05/creating-haskell-notebooks-with-org-mode/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.