简体   繁体   中英

Constructors as datatypes in Haskell

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?

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM