0

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?

3 Answers 3

2

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
Sign up to request clarification or add additional context in comments.

4 Comments

I'm sorry if I'm missing something but how would this allow me to keep a list of different Columns?
It wasn't obvious from the question that was a requirement.
@PetrasPurlys Unfortunately those two features are incompatible: either the type system distinguishes the two things, so you can write a function which only accepts number columns, OR the type system does not distinguish the two things, so you can put them both in a list. If you want both features, you need two (collections of) types, one distinguishable and one not.
@PetrasPurlys You can move the a to the right and get back the power you wanted. data Column = forall a. ToColumn a => Column a (but then you'll lose and trace of a in type signatures)
0

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 NumCols only, rather than Columns or NumberColumns:

sumColumn :: NumCol -> NumCol -> NumCol
sumColumn (NumCol s1 d1) (NumCol s2 d2) = ...

EDIT:

If you want NumCols to behave like NumberColumns, 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 NumCols and Columns interchangeably. For example:

colFunction :: Column -> Column
colFunction = ...

becomes

colFunction :: Columnlike a => a -> a
colFunction = ...

and you can then use colFunction on NumCols and NumberColumns alike.

2 Comments

That would work but this is a matter of readability. NumCol and NumberColumn are/represent the same thing so I would very much like to have a single name.
@PetrasPurlys There is likely no free lunch there. You can, however, make NumCol and NumberColumn behave similarly, see my edit. Another option is to define sumColumn over pairs (String, [Double]), but this also has its downsides.
0

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 as.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.