I'm having trouble modeling a data structure in Haskell. Suppose I'm running an an animal research facility and I want to keep track of my rats. I want to track the assignment of the rats to cages and to experiments. I also want to keep track of the weight of my rats, the volume of my cages, and keep notes on my experiments.
In SQL, I might do:
create table cages (id integer primary key, volume double);
create table experiments (id integer primary key, notes text)
create table rats (
    weight double,
    cage_id integer references cages (id),
    experiment_id integer references experiments (id)
);
(I realize that this allows me to assign two rats from different experiments to the same cage. That is intended. I don't actually run an animal research facility.)
Two operations that must be possible: (1) given a rat, find the volume of its cage and (2) given a rat, get the notes for the experiment it belongs to.
In SQL, those would be
select cages.volume from rats
  inner join cages on cages.id = rats.cage_id
  where rats.id = ...; -- (1)
select experiments.notes from rats
  inner join experiments on experiments.id = rats.experiment_id
  where rats.id = ...; -- (2)
How might I model this data structure in Haskell?
One way to do it is
type Weight = Double
type Volume = Double
data Rat = Rat Cage Experiment Weight
data Cage = Cage Volume
data Experiment = Experiment String
data ResearchFacility = ResearchFacility [Rat]
ratCageVolume :: Rat -> Volume
ratCageVolume (Rat (Cage volume) _ _) = volume
ratExperimentNotes :: Rat -> String
ratExperimentNotes (Rat _ (Experiment notes) _) = notes
But wouldn't this structure introduce a bunch of copies of the Cages and Experiments? Or should I just not worry about it and hope the optimizer takes care of that?
On the left hand side of the => you declare constraints for the types that are used on the right. In the example you give, it means that a is constrained to being an instance of both the Ord type class and the Num type class. Follow this answer to receive notifications.
Functions also have a type. It can (and should) be explicitly declared. The type A -> B -> C indicates a function that takes two arguments of type A and B , and returns a C .
Here's a short file I used for testing:
type Weight = Double
type Volume = Double
data Rat = Rat Cage Experiment Weight deriving (Eq, Ord, Show, Read)
data Cage = Cage Volume               deriving (Eq, Ord, Show, Read)
data Experiment = Experiment String   deriving (Eq, Ord, Show, Read)
volume     = 30
name       = "foo"
weight     = 15
cage       = Cage volume
experiment = Experiment name
rat        = Rat cage experiment weight
Then I started ghci and imported System.Vacuum.Cairo, available from the delightful vacuum-cairo package.
*Main System.Vacuum.Cairo> view (rat, Rat (Cage 30) (Experiment "foo") 15)

*Main System.Vacuum.Cairo> view (rat, Rat (Cage 30) experiment 15)

(I'm not really sure why there's doubled-up arrows in this one, but you can ignore/collapse them.)
*Main System.Vacuum.Cairo> view (rat, Rat cage experiment weight)

*Main System.Vacuum.Cairo> view (rat, rat)

*Main System.Vacuum.Cairo> view (rat, Rat cage experiment (weight+1))

The rule of thumb, as should be illustrated above, is that new objects are created exactly when you call a constructor; otherwise, if you just name an already-created object, no new object is created. This is a safe thing to do in Haskell because it is an immutable language.
A more natural Haskell representation of your model would be for the cages to contain the actual rat objects instead of their ids:
data Rat = Rat RatId Weight
data Cage = Cage [Rat] Volume
data Experiment = Experiment [Rat] String
Then you would create ResearchFacility objects using a smart constructor to make sure they follow the rules. It can look something like:
research_facility :: [Rat] -> Map Rat Cage -> Map Rat Experiment -> ResearchFacility
research_facility rats cage_assign experiment_assign = ...
where the cage_assign and experiment_assign are maps which contain the same information as the cage_id and experiment_id foreign keys in sql.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With