From the REPL to a Package

When learning a programming language, building a package should be considered equivalent to emancipation.

This is because only by creating small reusable components one can eventually build larger complicated systems. And packages are the unit for this. So building your own package, putting it on a package repository for your language and iterating over it is pretty much a baseline for any language you work with.

So here I am, ready to leave the world of loading files into the Haskell REPL and starting to load packaged modules instead.

This post is specifically about moving the functions I created to generate prime numbers for Project Euler problems into a separate package where they can be referred to from other places. So I started by moving the file in question (Problem007.hs) into a separate directory, I gitified it and made it available on github. The file was also renamed to Euler.hs as that will be the entry point for the library.

So I currently have in ~/dev/euler one file — Euler.hs. Let’s start then:

  • cabal init

cabal init is super detailed in all the questions it asks:

Package name? [default: euler] 
Package version? [default:] 0.1.0
Please choose a license:
 * 1) (none)
   2) GPL-2
   3) GPL-3
   4) LGPL-2.1
   5) LGPL-3
   6) AGPL-3
   7) BSD2
   8) BSD3
   9) MIT
   10) ISC
   11) MPL-2.0
   12) Apache-2.0
   13) PublicDomain
   14) AllRightsReserved
   15) Other (specify)
Your choice? [default: (none)] 9
Author name? [default: Luis] Luis Rodrigues Soares
Maintainer email? [default:] 
Project homepage URL?
Project synopsis? Mathematics utilities for Haskell
Project category:
 * 1) (none)
   2) Codec
   3) Concurrency
   4) Control
   5) Data
   6) Database
   7) Development
   8) Distribution
   9) Game
   10) Graphics
   11) Language
   12) Math
   13) Network
   14) Sound
   15) System
   16) Testing
   17) Text
   18) Web
   19) Other (specify)
Your choice? [default: (none)] 12
What does the package build:
  1) Library
  2) Executable
Your choice? 1
What base language is the package written in:
* 1) Haskell2010
  2) Haskell98
  3) Other (specify)
Your choice? [default: Haskell2010] 1
Include documentation on what each field means (y/n)? [default: n] y
Source directory:
* 1) (none)
  2) src
  3) Other (specify)
Your choice? [default: (none)] 2

Guessing dependencies...

Generating LICENSE...
Warning: LICENSE already exists, backing up old version in LICENSE.save0
Generating Setup.hs...
Generating euler.cabal...

You may want to edit the .cabal file and add a Description field.

And after it runs, I now have the following in my project’s directory:

drwxr-xr-x 8 luis luis 4096 Feb 21 11:04 .git/
-rw-r--r-- 1 luis luis  107 Feb 21 10:53 .gitignore
-rw-r--r-- 1 luis luis 1065 Feb 21 11:01 LICENSE
-rw-r--r-- 1 luis luis  170 Feb 21 10:56
-rw-r--r-- 1 luis luis   46 Feb 21 11:01 Setup.hs
-rw-r--r-- 1 luis luis 2053 Feb 21 11:01 euler.cabal
drwxr-xr-x 2 luis luis 4096 Feb 21 11:03 src/

Note that I moved Euler.hs into src/. Also, there is one thing we should guarantee here: that euler.cabal contains a reference to module Euler in the exposed-modules section, like so:

exposed-modules: Euler

So with that in place, we can now do cabal configure:

Resolving dependencies...
Configuring euler-0.1.0...

and then cabal build:

Building euler-0.1.0...
Preprocessing library euler-0.1.0...
[1 of 1] Compiling Euler            ( src/Euler.hs, dist/build/Euler.o )
In-place registering euler-0.1.0...

If after this we do cabal clean we’ll clean the whole thing up.

So the project is now cabalized. Our next step is to put it into Hackage. Let’s run cabal configure and cabal build again and after this we prepare our package with cabal sdist:

Distribution quality warnings:
No 'description' field.
When distributing packages it is encouraged to specify source control
information in the .cabal file using one or more 'source-repository' sections.
See the Cabal user guide for details.
Building source dist for euler-0.1.0...
Preprocessing library euler-0.1.0...
Source tarball created: dist/euler-0.1.0.tar.gz

So there’s some fields we forgot to fill in but since these are only warnings let’s silently ignore them. We have our tar.gz file and now we should head over to Hackage, create an account and then upload our tar.gz; after this our package is now in Hackage.

So now let’s see if that really works.

Open another terminal, in some test directory, and let’s do:

  • cabal update
  • cabal install euler

which should succeed with:

Resolving dependencies...
Downloading euler-0.1.0...
Configuring euler-0.1.0...
Building euler-0.1.0...
Installed euler-0.1.0

if now we fire up ghci and try our package:

GHCi, version 7.8.4:  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :module +Euler
Prelude Euler> trialAndDivision 50
Loading package euler-0.1.0 ... linking ... done.

And there we have it! What’s left to do?

  • Adding the rest of fields missing in euler.cabal;
  • There’s a warning about global spacenames when I uploaded the file, have to find out what this is;
  • Add tests;
  • Add linting;
  • Add documentation.

But even without those in place, we now have a workflow to create Haskell packages and push them over to Hackage, so that they can be used by others.