3

I have some types

data Foo = Foo
data Bar = Bar 
data Baz = Baz

I want to use them as keys for a Map. Is this possible, and if so, how?

Additonal context below:

I have an application that builds VMs. I have seperated the work into Phases. Currently I have this type

data CurrentPhase = PHASEONE
                  | PHASETWO
                  | PHASETHREE (deriving Eq,Ord)

So far so good right, no problems like what I mentioned above. However, I made a type class to describe operations that are phase specific

class PhaseOps phase where
  preValidate :: JobID -> phase -> Handler (Status)
  doPreProc :: JobID -> phase -> Handler (Status)
  updateConfig :: JobID -> phase -> Handler ()
  postValidate :: JobID -> phase -> Handler (Status)

in order for this to work, I had to create a new set of singleton data types to use for PhaseOps instances.

data PhaseOne = PhaseOne

.. and so on

now I have these singleton types, and CurrentPhase. I'd like to get rid of CurrentPhase (which I am using for a Map with CurrentPhase being the key), and use my singleton data types.

15
  • 1
    Is there a problem with data FooBarBaz = Foo | Bar | Baz? (Specifically, why doesn't this work?) Commented Sep 10, 2012 at 18:29
  • 3
    There is a problem with storing different type keys in a single map, I mean how will you compare two values of different types ? The best you can do is create a union of Maps for each type and use type classes (or type families) to write your own wrappers around it. Commented Sep 10, 2012 at 18:40
  • 1
    I begining to think that a Map is the wrong data structure to use. Commented Sep 10, 2012 at 18:59
  • 1
    @MichaelLitchard: Well, if you simply told us what problem you're trying to solve we could tell you if that was the case. Don't ask for helps to complete a step, ask for help to reach a goal. Sometimes the steps you're trying to take to the goal are only wrong and misleading. (And sometimes not, but why chance it?) Commented Sep 10, 2012 at 19:00
  • 4
    I note that PhaseOps looks a great deal like a class that wants to be a record of functions instead. Commented Sep 10, 2012 at 19:11

1 Answer 1

4

The straightforward solution is to use keys of type Either Foo (Either Bar Baz). This gets verbose quickly as you add possible types and is a bit ugly anyway, so it often makes more sense to use a special-purpose equivalent such as:

data FooBarBaz = FooVal Foo | BarVal Bar | BazVal Baz

This is similar to combining them into one type directly, but trades off a bit more verbosity in the combined type for being able to still use the individual types elsewhere. This is a relatively common pattern; for instance, I've seen it often in types representing syntax trees, where a "top level declaration" type might take this form, with each kind of declaration being its own separate type.

Depending on the nature of your problem there might be other approaches that would be better, but the above is the only good general-purpose solution I can think of--if you don't like doing it this way, you'll need to more clearly specify why and elaborate somewhat on what you need these types to accomplish.


Edit in response to clarification:

As I mentioned in the comments on the question, PhaseOps looks a great deal like a class that wants to be a record of functions instead. Furthermore, if you have such a class, wanting a way to work with multiple instance types as if they were a single type is a very strong indication that it's time to step back and rethink your design.

Continuing with such a design almost always leads to either mucking about with Typeable, as Thomas M. DuBuisson mentions in the comments, or to mucking about with existential types (which is a well-known anti-pattern these days). It's true that such approaches are occasionally required, but are best avoided unless you can very clearly explain (even if only to yourself) why you need them. Otherwise, they create far more problems than they solve.

Incidentally, if you want to retain some of the benefits of separate types, I'd consider using your singleton types for phantom type tagging and/or hiding the constructor for the PhaseOps record and using a smart constructor that takes a CurrentPhase argument.

Sign up to request clarification or add additional context in comments.

1 Comment

@MichaelLitchard: Edited the answer to match the comment. :]

Your Answer

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