DEV Community

Cover image for Creating Haskell notebooks with org-mode
Laura Viglioni
Laura Viglioni

Posted on

Creating Haskell notebooks with org-mode

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
         ;; ...
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]]         
Enter fullscreen mode Exit fullscreen mode
    Prelude> [1,6,15,54]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
    :{
    map
      (\x -> x*x + x + 1)
      [1..10]
    :}
Enter fullscreen mode Exit fullscreen mode
    Prelude Control.Monad| Prelude Control.Monad| Prelude Control.Monad| Prelude Control.Monad| [3,7,13,21,31,43,57,73,91,111]
Enter fullscreen mode Exit fullscreen mode

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      
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

e.g.:

    :{
    map
      (\x -> x*x + x + 1)
      [1..10]
    :}
Enter fullscreen mode Exit fullscreen mode
   [3,7,13,21,31,43,57,73,91,111]
Enter fullscreen mode Exit fullscreen mode

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"))
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

On ~/.stack/global-project/stack.yaml add the following:

            packages:
              - org-haskell
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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

The header picture

Discussion (5)

Collapse
cipharius profile image
Valts Liepiņš

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:

:set prompt ""
Enter fullscreen mode Exit fullscreen mode
Collapse
viglioni profile image
Laura Viglioni Author

I didn't know that, it might work! :))

Collapse
rodryggoshell profile image
Rodrigo Martins

tem como vc me da uma ajuda??

Collapse
drbearhands profile image
DrBearhands

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.

Collapse
rodryggoshell profile image
Rodrigo Martins

Preicando da sua ajuda