Reputation: 1155
Currently I have these datatypes:
data NumberColumn = NumberColumn String [Double]
data StringColumn = StringColumn String [String]
data UnknownColumn = UnknownColumn String [String]
All of these datatypes (there are others too, these are just domain examples) model csv file columns. They can represent plain numbers, names, money, simple text and so on.
What I would like to achieve is something like this:
data Column = NumberColumn String [Double] | StringColumn String [String] | UnknownColumn String [String]
That is, I would like to put them in a single datatype so that it would be possible to map, filter and create new items, like so:
sumColumn :: NumberColumn -> NumberColumn -> NumberColumn
sumColumn...
The problem is that NumberColumn is not a datatype but a constructor, so the best that I can think of is to accept and return Column types:
sumColumn :: Column -> Column -> Column
sumColumn (NumberColumn...) (NumberColumn...)...
This works but the explicitness that the function should only take NumberColumns is lost and I would very much like to keep it.
Can this be achieved?
Upvotes: 0
Views: 102
Reputation: 16145
In addition to chepner's
data Column a = Column String [a]
you could define sumColumn
as
sumColumn :: Num a => Column a -> Column a -> Column a
sumColumn (Column name1 ms) (Column name2 ns) =
Column (name1 ++ "+" ++ name2) (zipWith (+) ms ns)
You could also use GADTs to ensure that columns that contain numbers are only used as such:
{-# LANGUAGE GADTs #-}
data Column a where
NumColumn :: Num a => String -> [a] -> Column a
StrColumn :: String -> [String] -> Column String
but you'd still have to constrain every function that operates on Num a => Column a
s.
Upvotes: 0
Reputation: 1282
You could factor the NumberColumn
constructor's data into a new datatype thusly:
data Column
= NumberColumn NumCol
| StringColumn String [String]
| UnknownColumn String [String]
data NumCol = NumCol String [Double]
sumColumn
is then defined over NumCol
s only, rather than Column
s or NumberColumn
s:
sumColumn :: NumCol -> NumCol -> NumCol
sumColumn (NumCol s1 d1) (NumCol s2 d2) = ...
EDIT:
If you want NumCol
s to behave like NumberColumn
s, you could use a type class:
class Columnlike a where
toColumn :: a -> Column
instance Columnlike Column where
toColumn = id
instance Columnlike NumCol where
toColumn = NumberColumn
With this type class at hand, your functions over Column
can now be over some Columnlike a
, and you can use NumCol
s and Column
s interchangeably. For example:
colFunction :: Column -> Column
colFunction = ...
becomes
colFunction :: Columnlike a => a -> a
colFunction = ...
and you can then use colFunction
on NumCol
s and NumberColumn
s alike.
Upvotes: 0
Reputation: 532518
It seems like you want a single type constructor Column
defined as
data Column a = Column String [a]
Then
sumColumn :: Column Double -> Column Double -> Column Double
sumColumn (Column name values) = ...
To distinguish between StringColumn
and UnknownColumn
from your original, use a new type for Unknown
to distinguish it from an "ordinary" string.
newtype Unknown = Unknown String
type UnknownColumn = Column Unknown -- for example
Upvotes: 2