views:

123

answers:

3

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.

A: 

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 Sanders
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
+3  A: 

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
Any suggestions what to do about the QuickCheck problem?
Ollie Saunders
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
A: 

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
You have a separate module for _all_ the `Arbitrary` instances?
Ollie Saunders
@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