Mark Rushakoff
Mark Rushakoff

Reputation: 258528

Haskell lists of datatypes

This is probably another easy Haskell question. If I have some "nested" data types, such as in this example code:

data Place = Country
           | State
           | City String
           deriving Show

data State = California
           | NewYork
           deriving Show

data Country = USA
             | Canada
             deriving Show

I can legally make a list such as [USA, Canada] of type [Country], or [California, NewYork] of type [State], or [City "a", City "b"] of type [Place].

What do I have to do to make a list such as [USA, NewYork]? NewYork is a State which is a Place, and USA is a Country which is a Place, but ghci sees USA so it assumes I am making a list of Countrys (and NewYork is a State, so the list fails).

I think I need some way to cast a Country or State to a Place, but I'm at a loss on how to accomplish this.

I'm trying to avoid throwing the data contained within State and Country into the Place type, which I know would make it work, but I've got a decent amount of real data that I'd rather not jumble up like that.

Upvotes: 6

Views: 2091

Answers (3)

Don Stewart
Don Stewart

Reputation: 138007

You're building a heterogeneous list (i.e. the list holds values of different types). Such a structure can be statically or dynamically typed. Either way, as long as we know the value supports a particular interface, we can use it in a list wrapped.

The nicest approach, IMO, is via existential typing, as ephemient shows:

  • all values support the Show interface
  • anything that supports Show can be put in the list
  • the type system guarantees you can't break the abstraction

Upvotes: 4

ephemient
ephemient

Reputation: 205014

{-# LANGUAGE ExistentialQuantification #-}
data GenericPlace = forall a. Show a => GenericPlace a
places :: [GenericPlace]
places = [GenericPlace USA, GenericPlace NewYork]

See the GHC user guide # 7.4.4 Existentially quantified data constructors for some restrictions with this approach.


There's a in-depth study of making usable heterogeneous collections in Haskell.

Strongly typed heterogeneous collections

A heterogeneous collection is a datatype that is capable of storing data of different types, while providing operations for look-up, update, iteration, and others. There are various kinds of heterogeneous collections, differing in representation, invariants, and access operations. We describe HList --- a Haskell library for strongly typed heterogeneous collections including extensible records. We illustrate HList's benefits in the context of type-safe database access in Haskell. The HList library relies on common extensions of Haskell 98. Our exploration raises interesting issues regarding Haskell's type system, in particular, avoidance of overlapping instances, and reification of type equality and type unification.

Upvotes: 7

Mark Rushakoff
Mark Rushakoff

Reputation: 258528

Here's a little more output that led me to figure out the problem:

*Main> [State, State]
[State,State]
*Main> :t State
State :: Place
*Main> :t NewYork
NewYork :: State

This seems to imply that the word "State" is a valid constructor for Place, and that the data State refers to a value of only California or NewYork.

If you change the program slightly, to:

data Place = Country Country
           | State State
           | City String
           deriving Show

data State = California
           | NewYork
           deriving Show

data Country = USA
             | Canada
             deriving Show

then you can make a list such as [Country USA, State NewYork] which is properly of type [Place]. Using the same word twice as in the first example does not "bind" the State type together in the way I had thought it would.

Of course, using the constructor State State is just a matter of preference, I could just as easily do AmericanState State within the Place type if I were so inclined.

Upvotes: 12

Related Questions