How to model an OLAP hypercube in Haskell

Viewed 97

My thoughts so far were:

Types

a hypercube is a (discrete) function from one or more Dimensions to some value. I wanted to use Enumeration types for the Dimensions and ended up with this typeclass:

class (Eq a, Ord a, Show a, Enum a, Bounded a) => Dimension a

which allowed me to use this

data Age = Young | Adult | Old    deriving (Eq, Ord, Show, Enum, Bounded)
instance Dimension Age

and similar types as Dimensions.

A 2dimensional cube where the values have the type Double then has the type

cube2D :: (Dimension a, Dimension b) => a -> b -> Double

Operations

I can rotate the cube easily:

cube2D' p a = cube2D a p

I can get all possible values ("arguments") of a Dimension and for a 1D cube I can also get the possible results

arguments :: Dimension a => [a]
arguments = enumFrom minBound

results :: (Dimension a) => (a -> r) -> [r]
results cube = fmap cube arguments

This allows me to collapse a cube by one dimension, e.g. by replacing it by the sum of all results:

sum2D :: (Dimension a, Dimension b, Num r) => (a->b->r) -> a -> r
sum2D = fmap (sum . results)

So far so good

but sum2D only works for a cube with two Dimensions. I can write a similar function for a 3D cube and another one for a 4D cube. I suspect that most yet-to-be implemented operations will face the same problem. That means, my abstraction of Hypercube isn't really worth its salt.

I am not sure if I started on the wrong foot, or if my attempt to have an arbitrary number of Dimensions just doesn't go well with type-safety.

Any suggestions will be much appreciated.

1 Answers

Dunno what the Eq and Ord constraints are, but I'd replace Enum and Bounded by Finite. For example:

{-# LANGUAGE DeriveAnyClass #-}
data Age = Young | Adult | Old deriving (Enum, Bounded, Universe, Finite)

At first it doesn't like like we've won yet. The difference here is that the existence of these:

instance (Finite a, Finite b) => Finite (a, b)
instance Finite ()

So, now, instead of a -> b -> Double, use (a,(b,())) -> Double. Now you can rotate the first two dimensions of an n-dimensional thing:

rotate :: ((a,(b,c)) -> r) -> ((b,(a,c)) -> r)
rotate cube2D (p,(a,rest)) = cube2D (a,(p,rest))

You can sum along all dimensions:

sumNd :: (Finite a, Num r) => (a->r) -> r
sumNd f = sum [f a | a <- universeF]
Related