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:
#+begin_src haskell :exports both :post org-babel-haskell-formatter(*this*)
<code>
#+end_src
e.g.:
:{
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
Top comments (10)
Since GHCi has a builtin way to hide prompt, couldn't you use that instead of formatting output of every code block?
If I recall correctly, it went like:
I didn't know that, it might work! :))
tem como vc me da uma ajuda??
Will have to try this myself. Jupyter has been the primary reason for using Python over Haskell for data science. Doing that in Haskell would be a huge improvement.
Thanks for this!
I have spacemacs with org and haskell layers on, and I have ghci in PATH -> but I get error message "org-babel-execute-src-block: No org-babel-execute function for haskell!". It seems like I am missing some part of my installation -> would you have any idea what might be missing? Thanks!
I fixed it by adding ob-haskell package to list of additional spacemacs packages and then just doing
(require 'ob-haskell)
in config!The tip about being able to use
:set
was very helpful! I used it to avoid having to use the multiline-brackets:{ ... :}
(which I personally find annoying). This is done via the line:set +m
, ideally in your code block that has imports and such, towards the top of the org file.Someone who knows more than epsilon about emacs might know how to get such options loaded in by default when opening ghci for org-babel, but until then... slightly awkward set commands for me it is.
Thanks for the post!
Preicando da sua ajuda
Hello
oi, só vi agora!
Me procura no twitter @LauraViglioni