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 is super detailed in all the questions it asks:
Package name? [default: euler] Package version? [default: 0.1.0.0] 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: firstname.lastname@example.org] Project homepage URL? https://github.com/decomputed/euler 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 README.md -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
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:
So with that in place, we can now do
Resolving dependencies... Configuring euler-0.1.0...
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
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 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: http://www.haskell.org/ghc/ :? 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. [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47]
And there we have it! What’s left to do?
- Adding the rest of fields missing in
- 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.