Search code examples
haskellpolymorphismoverloadingtypeclass

How do I get 'unpredictable' overloading on a return type working in Haskell?


I have some type instances. Let's call them A, B, and C. They all are instances of typeclass X. Now I would like to create a seperate function create that creates an instance of A, B or C given some input (let's say a string). The type system cannot know what input is going to give what type. That's the thing Haskell doesn't like, and I think I know the answer, but I want to be sure. The current error I'm getting is:

• Couldn't match expected type ‘c’ with actual type ‘GCCCommand’
  ‘c’ is a rigid type variable bound by
    the type signature for:
      compiler :: forall c. CompilerCommand c => String -> c
    at src/System/Command/Typed/CC.hs:29:1-44
• In the expression: gcc path
  In an equation for ‘compiler’:
      compiler path
        | exe == "g++" || exe == "gcc" || exe == "cc" || exe == "cpp"
        = gcc path
        where
            exe = takeFileName path
• Relevant bindings include
    compiler :: String -> c
      (bound at src/System/Command/Typed/CC.hs:31:1)

Does that mean, as I suspect, that it is not possible to overload on return type in this particular case because the compiler can't know upfront how the data will look in memory? How would you go to implement this function? I was thinking about creating something like the following:

data SuperX = SuperA A | SuperB B | SuperC C

create :: String -> SuperX
-- create can now be implemented

instance X SuperX where
  -- a lot of boilerplate code ...

However, the boilerplate code suggests that it can be done better. Is this really the best way for doing it?


Solution

  • It depends what you need to do with it.

    If your later processing doesn't care if it gets an A, a B, or C, just that it gets something that implements X...

    restOfProgram :: X a => a -> ThingIWantToCompute
    

    Then you could use continuation passing:

    parseABC :: (X a => a -> r) -> String -> Maybe r
    parseABC f "A" = Just (f A)
    parseABC f ('B':xs) = Just (f (B xs))
    parseABC f ('C':xs) = Just (f (C (read xs)))
    parseABC _ _ = Nothing
    

    Or an existential data wrapper:

    data SomeX where
      SomeX :: X t => t -> SomeX
    
    parseABC :: String -> Maybe SomeX
    parseABC "A" = Just (SomeX A)
    parseABC ('B':xs) = Just (SomeX (B xs))
    parseABC ('C':xs) = Just (SomeX (C (read xs)))
    parseABC _ _ = Nothing
    
    restOfProgram' :: SomeX -> ThingIWantToCompute
    restOfProgram' (SomeX t) = restOfProgram t
    

    If the later processing has different paths for A, B or C, you probably want to return a sum type like SuperX.