type classes and multiple implementations

I'm writing a tool for which there will be multiple storage formats. The user can choose his favorite storage mechanism through a configuration file. Possible storage mechanisms might include in-memory storage or on-disk storage. To represent this abstraction, I created a Storage type class and two simple instances: class Storage a where open :: String -> IO a data Disk = Disk instance Storage Disk where open file = do putStrLn $ "opening disk: " ++ file return Disk data Memory = Memory instance Storage Memory where open _ = do putStrLn "opening memory" return Memory I can load this code into ghci (version 6.10.1, if that matters) and it behaves as I expected it to: *Main> open "foo" :: IO Disk opening disk: foo *Main> open "foo" :: IO Memory opening memory Eventually a configuration facility will provide a string indicating which storage mechanism the user prefers. This will return a string like "memory" or "disk". Based on that string, I want to return a value whose type belongs to the Storage type class. I thought this would do the job: by_type :: Storage a => String -> String -> IO a by_type "disk" x = open x :: IO Disk by_type "memory" x = open x :: IO Memory by_type _ t = error $ "no such storage type " ++ t but I get the following compile errors: Couldn't match expected type `Disk' against inferred type `Memory' Expected type: IO a Inferred type: IO Memory In the expression: open x :: IO Memory In the definition of `by_type': by_type "memory" x = open x :: IO Memory It seems as though the type variable `a' is binding to Disk which prevents it from later binding to Memory. That makes sense but I don't see a way forward from here. How can I accomplish multiple implementations of the same interface? Are type classes the right way? Thank you for any help. -- Michael

by_type :: Storage a => String -> String -> IO a This function must, for any Storage type, take any two strings and produce an IO value of that type. (by_type "disk" "xyz" :: Memory must be valid.) It doesn't really have the option of choosing which instance of Storage to use. In pure Haskell, you would probably have to do something like type Storage = Disk Handle | Memory String by_type :: String -> String -> Storage That way, by_type can return any Storage it wants. I'm sure there are also ways to do what you want with extensions.

On Fri, Jun 5, 2009 at 7:09 PM, Sean Bartell
by_type :: Storage a => String -> String -> IO a This function must, for any Storage type, take any two strings and produce an IO value of that type. (by_type "disk" "xyz" :: Memory must be valid.) It doesn't really have the option of choosing which instance of Storage to use.
In pure Haskell, you would probably have to do something like type Storage = Disk Handle | Memory String by_type :: String -> String -> Storage That way, by_type can return any Storage it wants.
I'm sure there are also ways to do what you want with extensions.
I think the point is to be able to extend the storage methods in another module ? If not, Sean's solution is what you want. If you want to use the type class to allow anyone to add a new method in his own module, you can use existential type, like :
data Store = forall a . Storage a => Store a
and then your byType :
byType "disk" x = Store (open x :: Disk) byType "memory" x = Store (open x :: Memory)
See XMonad and its Layout type for an example of this method. -- Jedaï
participants (3)
-
Chaddaï Fouché
-
Michael Hendricks
-
Sean Bartell