views:

404

answers:

4

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.

+9  A: 

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.

Mark Rushakoff
Oh, is that what you were stumbling over? Yes, both constructors and types begin with upper-case characters, but are in different namespaces.
ephemient
Yeah, that was a real beginner's mistake... hopefully someone else finds it useful too.
Mark Rushakoff
Variable names (and type variable names) are lower-initial (or non-":" initial symbol), constructor names are upper-initial (or ":"-initial symbol), and type names (and module names) are upper-initial; there's no overlap between any of these categories. Hopefully it won't take you too long to get used to it :)
ephemient
+4  A: 
{-# 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.

ephemient
+2  A: 

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
Don Stewart
A: 

See related* SO question: haskell polymorphism and lists

*related/same

yairchu