6

I'm writing a snake game in Haskell. These are some of the things I have:

  • A Coord data type
  • A Line data type
  • A Rect data type
  • A Polygon type class, which allows me to get a Rect as a series of lines ([Line]).
  • An Impassable type class that allows me to get a Line as a series of Coords ([Coord]) so that I can detect collisions between other Impassables.
  • A Draw type class for anything that I want to draw to the screen (HSCurses).
  • Finally I'm using QuickCheck so I want to declare Arbitrary instances for a lot of these things.

Currently I have a lot of these in separate modules so I have lots of small modules. I've noticed that I have to import a lot of them for each other so I'm kind of wondering what the point was.

I'm particularly confused about Arbitrary instances. When using -Wall I get warnings about orphaned instances when I but those instances together in one test file, my understanding is that I can avoid that warning by putting those instances in the same module as where the data type is defined but then I'll need to import Test.QuickCheck for all those modules which seems silly because QuickCheck should only be required when building the test executable.

Any advice on the specific problem with QuickCheck would be appreciated as would guidance on the more general problem of how/where programs should be divided into modules.

Ollie Saunders
  • 7,787
  • 3
  • 29
  • 37

4 Answers4

4

You can have your cake and eat it too. You can re-export modules.

module Geometry 
    ( module Coord, module Line, module Rect, module Polygon, module Impassable )
where

I usually use a module whenever I have a complete abstraction -- i.e. when a data type's meaning differs from its implementation. Knowing little about your code, I would probably group Polygon and Impassable together, perhaps making a Collision data type to represent what they return. But Coord, Line, and Rect seem like good abstractions and they probably deserve their own modules.

luqui
  • 59,485
  • 12
  • 145
  • 204
  • Any suggestions what to do about the QuickCheck problem? – Ollie Saunders Oct 23 '10 at 10:54
  • 1
    That is a difficult problem, one that I have been working on, actually. I think your best option right now is just to deal with the orphans as John suggests. I think the right way to do it is to have "distribution combinators", so instead of `instance Arbitrary Coord`, you have a function `arbitraryCoord :: Distribution Coord`. It is a bit more cumbersome to use in tests, but it is more flexible and modular, since you can have multiple distributions for the same type (in particular, if two different distributions are defined, the world does not explode). – luqui Oct 23 '10 at 18:44
2

For testing purposes, I use separate modules for the Arbitrary instances. Although I generally avoid orphan instances, these modules only get built when building the test executable so I don't mind the orphans or that it's not -Wall clean. You can also use -fno-warn-orphans to disable just this warning message.

John L
  • 27,937
  • 4
  • 73
  • 88
  • You have a separate module for _all_ the `Arbitrary` instances? – Ollie Saunders Oct 23 '10 at 10:46
  • @Ollie Saunders - it depends on the size of the project. For smaller projects, I use two modules for testing: one has `Arbitrary` instances and other extra code if necessary, and the other module has the QuickCheck properties. For larger projects, I like the layout used in the Snap framework. They have a test module for each regular module that includes Arbitrary instances and property tests. – John L Oct 23 '10 at 13:20
1

I generally put more emphasis on the module interface as defined by the functions it exposes rather than the data types it exposes. Do some of your types share a common set of functions? Then I would put them in the same module.

But my practise is probably not the best since I usually write small programs. I would advise looking at some code from Hackage to see what package maintainers do.

If there were a way to sort packages by community rating or number of downloads, that would be a good place to start. (I thought there was, but now that I look for it, I can't find it.) Failing that, look at packages that you already use.

Nathan Shively-Sanders
  • 18,329
  • 4
  • 46
  • 56
  • There are links for rankings by number of downloads and number of reverse dependencies here: http://stackoverflow.com/questions/3663550/which-haskell-package-for-json/3663601#3663601 – Travis Brown Oct 23 '10 at 01:32
0

One solution with QuickCheck is to use the C preprocessor to selectively enable the Arbitrary instances when you are testing. You put the Arbitrary instances straight into your main modules but wrap them with preprocessor macros, then put a "test" flag into your Cabal file.

massysett
  • 1,100
  • 6
  • 13