Dahan
Dahan

Reputation: 163

Haskell List Monad — generalize by parameter

Let's imagine a population (of humans). Each human has things, that have length (left arm, right arm, left leg, right leg). Each human has things, that may have width (chest, waist). There are much more parameters that a human may have.

All measurement types are

allMeasurementTypes = [Width, Length, Diameter, Weight] -- and similar things.

For statistics I want to get for all population all possible pairs of measurements.

Something like that:

Pair1_LeftLeg Pair1_Waist Pair2_LeftLeg Pair2_Chest Pair3_RightLeg Pair3_Waist...
102           70          102           90          101            70         ...
115           60          115           85          116            60         ...
...           ...         ...           ...         ...            ...        ...

Don't tell me, that there are duplications here. I know. But it is easier for me to have it like that to process further in statistical applications.

So I construct this in the following way

allMeasurements :: [Human] -> Seq (Int, [(String, [Double])])
allMeasurements population = mapWithIndex (,) measures
  where
    measures = do
      mTypes <- fromList $ choose allMeasurementTypes 2
      rec1 <- recordsForPeople population $ head mTypes
      rec2 <- recordsForPeople population $ mTypes !! 1
      return [rec1, rec2]

And now I explain it line by line.

allMeasurements :: [Human] -> Seq (Int, [(String, [Double])])

Here Int is the number of the pair. If it is Pair1, then String would be "LeftLeg" and then "Waist". [Double] is the array of values of each measurement on all population.

allMeasurements population = mapWithIndex (,) measures
  where
    measures = do
      mTypes <- fromList $ choose allMeasurementTypes 2

This iterates through all possible pairs of measurement types. choose gets all possible pairs like [Width, Length], [Width, Diameter]...

      rec1 <- recordsForPeople population $ head mTypes

Assuming, that first mType is Length, rec1 iterates through all possible lengths. rec1 iterates through ("LeftLeg", [102, 115, ...]), ("RightLeg", [101, 116, ...])...

      rec2 <- recordsForPeople population $ mTypes !! 1

Assuming, that second mType is Width, rec2 iterates through all possible widths. rec2 iterates through ("Waist", [70, 60, ...]), ("Chest", [90, 85, ...])...

      return [rec1, rec2]

And finally I collect together all everything. It gives full Seq of all possible pairs of individual measurements for all population. mapWithIndex above just runs through this full list and injects actual Int number of pair inside. I chose Seq actually only because it has this mapWithIndex. Then it all goes out for rendering to csv or json.

Maybe I might do it better and smarter. Uff.

Here comes the real question.

Besides table of pairs, I also want to generate table of trinities.

allTriMeasurements :: [Human] -> Seq (Int, [(String, [Double])])
allTriMeasurements population = mapWithIndex (,) measures
  where
    measures = do
      mTypes <- fromList $ choose allMeasurementTypes 3
      rec1 <- recordsForPeople population $ head mTypes
      rec2 <- recordsForPeople population $ mTypes !! 1
      rec3 <- recordsForPeople population $ mTypes !! 2
      return [rec1, rec2, rec3]

Is it anyhow possible to generalize these two functions allMeasurements and allTriMeasurements by parameter n, defining if it is 2 for pairs or 3 for trinities.

I already dropped the ideas about fmap and similar, because here for (monadic) list comprehension all rec_i <- ... should go one after another. And any kind of doSomething <$> [0 .. (n-1)] breaks the structure.

It is pretty possible, that I did it all wrong. If anyone besides me is able to understand, what is going on here, I would greatly appreciate any ideas. As it seems to me the ugliest place in my code.

Upvotes: 1

Views: 94

Answers (1)

Dahan
Dahan

Reputation: 163

allMeasurements :: Int -> [Human] -> Seq (Int, [(String, [Double])])
allMeasurements k population = mapWithIndex (,) measures
  where
    measures = mapM (recordsForPeople population) =<< fromList (choose allMeasurementTypes k)

Soo easy. I just overlooked mapM superpower.

Upvotes: 1

Related Questions