[Haskell-beginners] Looking for a data structure

Calvin Smith cs-haskell at protempore.net
Tue Sep 16 15:21:15 EDT 2008


Hi Levi,

On 09/15/2008 04:11 PM,, Levi Stephen wrote:
 > ...
 >
 > What is the best way to structure this data and hopefully at the type 
level enforce this subset type relationship?
 >

It's not enforced at the type level as you desired, but in terms of 
enforcing the subset relationships, one approach is to use smart 
constructors [1]. This involves not exporting the constructor for the 
ADT, so that clients are not able to create possibly incorrect values, 
and instead providing functions for creating new values that ensure your 
constraints are satisfied.

In your case, if you wanted to ensure that a `Club` contains `Player`s 
that all belong to the same `League`, you would not export the `Club` 
constructor and would instead provide a function for creating new clubs. 
That function might accept a `League`, a name for the club, and some 
players, and the function might return something like `Either String 
Club`, where the string value would be an error message or the Club 
value would be the new club. In the `club` function that creates a new 
`Club`, you would verify that all the `Player`s belong to the given 
`League`.

That function (in a module with the appropriate export list) might look 
something like the following:

-- BEGIN EXAMPLE --

module Football(
   -- We export each data type but not its constructor, and provide a "smart
   -- constructor" function for each type. The client can only create values
   -- of a given type using the smart constructor, which ensures that all
   -- constraints such as subset relationships are satisfied.
   League(), league,
   Club(), club,
   Player(), player
) where

-- Define the types...

data League = League [Club] [Player]
data Club   = Club   String [Player]
data Player = Player String

-- Provide smart constructors that check whatever needs to be
-- checked and fail with an error message if necessary...

club :: League -> String -> [Player] -> Either String Club
club league name players =
   if all_players_in_league league players
      then Right (Club name players)
      else  Left "All players must be in the given league."
   where
      all_players_in_league :: League -> [Player] -> Bool
      all_players_in_league = undefined -- TODO

-- Do likewise for league and player...

league = undefined -- TODO
player = undefined -- TODO

-- END EXAMPLE --

Of course, you might want to verify more than shown. And since the 
`club` function creates a new `Club` in a given `League`, we would need 
to reflect that in the return type of `club` (e.g., `Either String 
(League, Club)` where the league value returned would have the new club 
added to it), but you get the idea.

One downside to this approach is that since we don't export the 
constructors, users of the module can't pattern match on the various 
types, so you also have to provide accessor functions for the various 
types. You can use the Haskell record syntax for defining the types [2], 
but even with the accessors automatically provided, not being able to 
pattern match can lead to more and uglier code, since we can no longer 
have functions like `f (Club league name players) = ...` and are forced 
to accept just a club value and manually extract what we need.

I hope that helps.

calvin

[1] http://www.haskell.org/haskellwiki/Smart_constructors
[2] 
http://en.wikibooks.org/wiki/Haskell/More_on_datatypes#Named_Fields_.28Record_Syntax.29


More information about the Beginners mailing list