Difference between revisions of "How to write a Haskell program"

From HaskellWiki
Jump to navigation Jump to search
(and how to integrate quickCheck into the repo)
(walk through again)
Line 36: Line 36:
   
 
<haskell>
 
<haskell>
$ vim Haq.hs
+
$ cat > Haq.hs
 
--
 
--
 
-- Copyright (c) 2006 Don Stewart - http://www.cse.unsw.edu.au/~dons
 
-- 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)
 
-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html)
 
--
 
--
  +
import System.Environment
   
 
-- | 'main' runs the main program
 
-- | 'main' runs the main program
 
main :: IO ()
 
main :: IO ()
  +
main = getArgs >>= print . haqify . head
main = do
 
  +
s <- getContents
 
print $ "Haq! " ++ s
+
haqify s = "Haq! " ++ s
 
</haskell>
 
</haskell>
   
Line 55: Line 56:
 
<haskell>
 
<haskell>
 
$ darcs init
 
$ darcs init
 
$ darcs add Haq.hs
$ ls
 
Haq.hs _darcs
 
$ darcs add Haq.hs
 
 
$ darcs record
 
$ darcs record
 
addfile ./Haq.hs
 
addfile ./Haq.hs
Line 66: Line 65:
 
+-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html)
 
+-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html)
 
+--
 
+--
  +
+import System.Environment
 
+
 
+
 
+-- | 'main' runs the main program
 
+-- | 'main' runs the main program
 
+main :: IO ()
 
+main :: IO ()
  +
+main = getArgs >>= print . haqify . head
+main = do
 
  +
+
+ s <- getContents
 
+ print $ "Haq! " ++ s
+
+haqify s = "Haq! " ++ s
 
Shall I record this change? (2/?) [ynWsfqadjkc], or ? for help: y
 
Shall I record this change? (2/?) [ynWsfqadjkc], or ? for help: y
What is the patch name? Initial import of cool new Haq project
+
What is the patch name? Import haq source
 
Do you want to add a long comment? [yn]n
 
Do you want to add a long comment? [yn]n
Finished recording patch 'Initial import of cool new Haq project'
+
Finished recording patch 'Import haq source'
  +
</haskell>
  +
  +
And we can see now darcs is running the show:
  +
  +
<haskell>
 
$ ls
 
Haq.hs _darcs
 
</haskell>
 
</haskell>
   
Line 105: Line 112:
 
import Distribution.Simple
 
import Distribution.Simple
 
main = defaultMainWithHooks defaultUserHooks
 
main = defaultMainWithHooks defaultUserHooks
  +
</haskell>
  +
  +
And record your changes:
  +
  +
<haskell>
  +
$ 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'
 
</haskell>
 
</haskell>
   
 
=== Build your project ===
 
=== Build your project ===
   
And now build it!
+
Now build it!
   
 
<haskell>
 
<haskell>
Line 121: Line 138:
 
And now you can run your cool project:
 
And now you can run your cool project:
 
<haskell>
 
<haskell>
$ haq
+
$ haq me
me
+
"Haq! me"
"Haq! me\n"
 
 
</haskell>
 
</haskell>
   
 
You can also run it inplace, avoiding the install phase:
 
You can also run it inplace, avoiding the install phase:
 
<haskell>
 
<haskell>
$ dist/build/haq/haq
+
$ dist/build/haq/haq you
you
+
"Haq! you"
"Haq! you\n"
 
 
</haskell>
 
</haskell>
   
Line 139: Line 154:
 
<haskell>
 
<haskell>
 
$ runhaskell Setup.hs haddock
 
$ runhaskell Setup.hs haddock
  +
</haskell>
  +
  +
which generates files in dist/doc/ including:
  +
  +
<haskell>
  +
$ 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
 
</haskell>
 
</haskell>
   
Line 147: Line 180:
   
 
<haskell>
 
<haskell>
  +
$ cat > Tests.hs
 
import Char
 
import Char
 
import List
 
import List
Line 157: Line 191:
 
arbitrary = choose ('\0', '\128')
 
arbitrary = choose ('\0', '\128')
 
coarbitrary c = variant (ord c `rem` 4)
 
coarbitrary c = variant (ord c `rem` 4)
 
tests = []
 
 
</haskell>
 
</haskell>
   
Line 164: Line 196:
   
 
<haskell>
 
<haskell>
  +
$ cat >> Tests.hs
 
-- reversing twice a finite list, is the same as identity
 
-- reversing twice a finite list, is the same as identity
 
prop_reversereverse s = (reverse . reverse) s == id s
 
prop_reversereverse s = (reverse . reverse) s == id s
Line 211: Line 244:
   
 
<haskell>
 
<haskell>
$ darcs record Haq.hs
+
$ darcs setpref test "runhaskell Tests.hs"
  +
Changing value of test from '' to 'runhaskell Tests.hs'
Recording changes in "Haq.hs":
 
  +
$ darcs add Tests.hs
 
  +
$ darcs record --all
hunk ./Haq.hs 11
 
  +
What is the patch name? Add testsuite
+-- | Haqifies a string
 
Shall I record this change? (1/?) [ynWsfqadjkc], or ? for help: y
 
What is the patch name? more docs
 
 
Do you want to add a long comment? [yn]n
 
Do you want to add a long comment? [yn]n
 
Running test...
 
Running test...
Line 224: Line 255:
 
Test ran successfully.
 
Test ran successfully.
 
Looks like a good patch.
 
Looks like a good patch.
Finished recording patch 'more docs'
+
Finished recording patch 'Add testsuite'
 
</haskell>
 
</haskell>
   
Line 246: Line 277:
 
</haskell>
 
</haskell>
   
And you're off.
+
And you're all set up!
   
 
== Licenses ==
 
== Licenses ==

Revision as of 04:30, 19 November 2006

A guide to the best practice for creating a new Haskell project or program.

Structure

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.

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 now darcs is 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:          dons@cse.unsw.edu.au
Build-Depends:       base

Executable:          haq
Main-is:             Haq.hs
ghc-options:         -O

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/dons
$ runhaskell Setup.hs build
$ runhaskell Setup.hs install

Run it

And now you can run your cool project:

$ haq me
"Haq! me"

You can also run it inplace, 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. So 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.

Great!

Running the testsuite from darcs

We can arrange for darcs to run the testsuite on every commit:

$ darcs setpref test "runhaskell Tests.hs"

will run the full set of QuickChecks. Let's commit a new patch:

$ darcs setpref test "runhaskell Tests.hs"
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 testsuite 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!

Licenses

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.

Revision control

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.

Advice:

  • Tag each release

Releases

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

For example:

$ 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

And 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

Hosting

A Darcs repository can be publised 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

Web page

Create a web page documenting your project! An easy way to do this is to add a project specific page to the Haskell wiki

Build system

Use Cabal.

Example Setup

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

Documentation

Use Haddock.

Testing

Pure code can be tested using QuickCheck or SmallCheck. Impure code with HUnit.

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.

Program structure

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.

Publicity

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 haskell@haskell.org! 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
    • Write about it on your blog
    • Then email the Planet Haskell maintainer (ibid on #haskell) the RSS feed url for your blog
  • Add your library or tool to the Libraries and tools page, under the relevant category, so people can find it.