How to write a Haskell program
A guide to the best practice for creating a new Haskell project or program.
- 1 Structure
- 1.1 Create a directory
- 1.2 Write some Haskell source
- 1.3 Stick it in darcs
- 1.4 Add a build system
- 1.5 Build your project
- 1.6 Run it
- 1.7 Build some haddock documentation
- 1.8 Add some automated testing: QuickCheck
- 1.9 Running the test suite from darcs
- 1.10 Tag the stable version, create a tarball, and sell it!
- 1.11 Summary
- 2 Automation
- 3 Licenses
- 4 Revision control
- 5 Releases
- 6 Hosting
- 7 Web page
- 8 Build system
- 9 Documentation
- 10 Testing
- 11 Program structure
- 12 Publicity
The basic structure of a new Haskell project can be adopted from HNop, the minimal Haskell project. It consists of the following files, for the mythical project "haq".
- Haq.hs -- the main haskell source file
- haq.cabal -- the cabal build description
- Setup.hs -- build script itself
- _darcs -- revision control
- README -- info
- LICENSE -- license
You can of course elaborate on this, with subdirectories and multiple modules.
Here is a transcript on how you'd create a minimal darcs and cabalised Haskell project, for the cool new Haskell program "haq", build it, install it and release.
The new tool 'mkcabal' automates all this for you, but its important to understand all the parts first.
Create a directory
Create somewhere for the source:
$ mkdir haq $ cd haq
Write some Haskell source
Write your program:
$ cat > Haq.hs -- -- Copyright (c) 2006 Don Stewart - http://www.cse.unsw.edu.au/~dons -- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html) -- import System.Environment -- | 'main' runs the main program main :: IO () main = getArgs >>= print . haqify . head haqify s = "Haq! " ++ s
Stick it in darcs
Place the source under revision control:
$ darcs init $ darcs add Haq.hs $ darcs record addfile ./Haq.hs Shall I record this change? (1/?) [ynWsfqadjkc], or ? for help: y hunk ./Haq.hs 1 +-- +-- Copyright (c) 2006 Don Stewart - http://www.cse.unsw.edu.au/~dons +-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html) +-- +import System.Environment + +-- | 'main' runs the main program +main :: IO () +main = getArgs >>= print . haqify . head + +haqify s = "Haq! " ++ s Shall I record this change? (2/?) [ynWsfqadjkc], or ? for help: y What is the patch name? Import haq source Do you want to add a long comment? [yn]n Finished recording patch 'Import haq source'
And we can see that darcs is now running the show:
$ ls Haq.hs _darcs
Add a build system
Create a .cabal file describing how to build your project:
$ cat > haq.cabal Name: haq Version: 0.0 Description: Super cool mega lambdas License: GPL License-file: LICENSE Author: Don Stewart Maintainer: firstname.lastname@example.org Build-Depends: base Executable: haq Main-is: Haq.hs ghc-options: -O
(You may need to use something like "base -any, haskell98 ==1.0" instead of just "base" for the "Build-Depends:" value). Add a Setup.hs that will actually do the building:
$ cat > Setup.hs #!/usr/bin/env runhaskell import Distribution.Simple main = defaultMainWithHooks defaultUserHooks
And record your changes:
$ darcs add haq.cabal Setup.hs $ darcs record --all What is the patch name? Add a build system Do you want to add a long comment? [yn]n Finished recording patch 'Add a build system'
Build your project
Now build it!
$ runhaskell Setup.hs configure --prefix=$HOME $ runhaskell Setup.hs build $ runhaskell Setup.hs install
And now you can run your cool project:
$ haq me "Haq! me"
You can also run it in-place, avoiding the install phase:
$ dist/build/haq/haq you "Haq! you"
Build some haddock documentation
Generate some API documentation into dist/doc/*
$ runhaskell Setup.hs haddock
which generates files in dist/doc/ including:
$ w3m -dump dist/doc/html/haq/Main.html haq Contents Index Main Synopsis main :: IO () Documentation main :: IO () main runs the main program Produced by Haddock version 0.7
Add some automated testing: QuickCheck
We'll use QuickCheck to specify a simple property of our Haq.hs code. Create a tests module, Tests.hs, with some QuickCheck boilerplate:
$ cat > Tests.hs import Char import List import Test.QuickCheck import Text.Printf main = mapM_ (\(s,a) -> printf "%-25s: " s >> a) tests instance Arbitrary Char where arbitrary = choose ('\0', '\128') coarbitrary c = variant (ord c `rem` 4)
Now let's write a simple property:
$ cat >> Tests.hs -- reversing twice a finite list, is the same as identity prop_reversereverse s = (reverse . reverse) s == id s where _ = s :: [Int] -- and add this to the tests list tests = [("reverse.reverse/id", test prop_reversereverse)]
We can now run this test, and have QuickCheck generate the test data:
$ runhaskell Tests.hs reverse.reverse/id : OK, passed 100 tests.
Let's add a test for the 'haqify' function:
-- Dropping the "Haq! " string is the same as identity prop_haq s = drop (length "Haq! ") (haqify s) == id s where haqify s = "Haq! " ++ s tests = [("reverse.reverse/id", test prop_reversereverse) ,("drop.haq/id", test prop_haq)]
and let's test that:
$ runhaskell Tests.hs reverse.reverse/id : OK, passed 100 tests. drop.haq/id : OK, passed 100 tests.
Running the test suite from darcs
We can arrange for darcs to run the test suite on every commit:
$ darcs setpref test "runhaskell Tests.hs"
will run the full set of QuickChecks. Let's commit a new patch:
Changing value of test from '' to 'runhaskell Tests.hs' $ darcs add Tests.hs $ darcs record --all What is the patch name? Add testsuite Do you want to add a long comment? [yn]n Running test... reverse.reverse/id : OK, passed 100 tests. drop.haq/id : OK, passed 100 tests. Test ran successfully. Looks like a good patch. Finished recording patch 'Add testsuite'
Excellent, now patches must pass the test suite before they can be committed.
Tag the stable version, create a tarball, and sell it!
Tag the stable version:
$ darcs tag What is the version name? 0.0 Finished tagging patch 'TAG 0.0'
Now generate a tarball:
$ darcs dist -d haq-0.0 Created dist as haq-0.0.tar.gz
And you're all set up!
The following files were created:
$ ls Haq.hs Tests.hs dist haq.cabal Setup.hs _darcs haq-0.0.tar.gz
A tool to automatically populate a new cabal project is available (beta!):
$ runhaskell mkcabal.hs Project name: haq Created Setup.hs and haq.cabal
which will fill out some stub Cabal files for the project 'haq'.
To create an entire project tree:
$ runhaskell mkcabal.hs --init-project Project name: ruby-on-rails-killer Created new project directory: ruby-on-rails-killer $ ls ruby-on-rails-killer LICENSE Ruby-on-rails-killer.hs ruby-on-rails-killer.cabal README Setup.hs
Code for the common base library package must be BSD licensed. Otherwise, it is entirely up to you as the author. Choose a licence (inspired by this). Check the licences of things you use, both other Haskell packages and C libraries, since these may impose conditions you must follow. Use the same licence as related projects, where possible. The Haskell community is split into 2 camps, roughly, those who release everything under BSD, and (L)GPLers. Some Haskellers recommend avoiding LGPL, due to cross module optimisation issues. Like many licensing questions, this advice is controversial. Several Haskell projects (wxHaskell, HaXml, etc) use the LGPL with an extra permissive clause which gets round the cross-module optimisation thing.
Use Darcs unless you have a specific reason not to. Almost all new Haskell projects are released under Darcs, and this benefits everyone -- a set of common tools increases productivity, and you're more likely to get patches.
- Tag each release
It's important to release your code as stable, tagged tarballs. Don't just rely on darcs for distribution.
- darcs dist generates tarballs directly from a darcs repository
$ cd fps $ ls Data LICENSE README Setup.hs TODO _darcs cbits dist fps.cabal tests $ darcs dist -d fps-0.8 Created dist as fps-0.8.tar.gz
You can now just post your fps-0.8.tar.gz
You can also have darcs do the equivalent of 'daily snapshots' for you by using a post-hook.
put the following in _darcs/prefs/defaults:
apply posthook darcs dist apply run-posthook
A Darcs repository can be published simply by making it available from a web page. If you don't have an account online, or prefer not to do this yourself, source can be hosted on darcs.haskell.org (you will need to email Simon Marlow to do this). haskell.org itself has some user accounts available.
There are also many free hosting places for open source, such as
Create a web page documenting your project! An easy way to do this is to add a project specific page to the Haskell wiki
Create a file called Setup.lhs with these contents:
#!/usr/bin/env runhaskell > import Distribution.Simple > main = defaultMain
Writing the setup file this way allows it to be executed directly by unix shells.
Example cabal file for Executables
Create the file myproject.cabal following this example:
Name: MyProject Version: 0.1 License: BSD3 Author: Your Name Build-Depends: base Synopsis: Example Cabal Executable File Executable: myproj Main-Is: Main.hs Other-Modules: Foo
Example cabal file for Libraries
Create the file myproject.cabal following this example:
Name: MyProj Version: 0.1 License: BSD3 Author: Your Name Build-Depends: base Synopsis: Example Cabal Library File Exposed-Modules: MyProject.Foo
For libraries, use Haddock.
To get started try, Introduction to QuickCheck. For a slightly more advanced introduction, here is a blog article about creating a testing framework for QuickCheck using some Template Haskell, Simple Unit Testing in Haskell.
Monad transformers are very useful for programming in the large, encapsulating state, and controlling side effects. To learn more about this approach, try Monad Transformers Step by Step.
The best code in the world is meaningless if nobody knows about it:
- Firstly, join the community! Subscribe to at least haskell-cafe@ and haskell@ mailing lists.
- Announce your project releases to email@example.com! This ensure it will then make it into the Haskell Weekly News. To be doubly sure, you should CC the release to the HWN editor
- Blog about it, on Planet Haskell
- Add your library or tool to the Libraries and tools page, under the relevant category, so people can find it.