Haskell : How to cast a data type to one of its specific typeclass?

  • A+
Category:Languages

I want to cast MySqlFakeClient and MySqlHttpClient to their common typeclass MySqlClient and I have this issue :

Haskell : How to cast a data type to one of its specific typeclass?

for the following code :

loadClient :: MySqlClient client => String -> client loadClient "fake" =  MySqlFakeClient 1 -- <- It's complaining here... loadClient "prod" =  MySqlHttpClient "http://www.google.com" loadClient _ = error "unknown"  data MySqlHttpClient = MySqlHttpClient String data MySqlFakeClient = MySqlFakeClient Int  class MySqlClient client where     config :: client -> String  instance MySqlClient MySqlHttpClient where    config (MySqlHttpClient url) = url  instance MySqlClient MySqlFakeClient where    config (MySqlFakeClient myInt) = show myInt 

can't we do it in Haskell ?


This is not how Haskell works. A type signature like

loadClient :: MySqlClient client => String -> client 

does not mean that client may be any type that has an instance for MySqlClient. It means (roughly) the caller gets to choose a type that has an instance MySqlClient and loadClient will return whatever the caller has chosen. This type is fixed at compile time, whereas you probably want something dynamic.

The way around this is, for this example, rather simple: If all you want is a String, just return that instead:

loadClient "fake" = show 1 loadClient "prod" = "http://www.google.com" 

Now you might think "my use case is more complicated than this, this will not work for me" - but you can easily extend this to more complicated cases. Basically, anything you would otherwise write in your class you can just put into a data instead (if you're not using things like TypeFamilies obviously, but that wouldn't make sense for dynamic things like these anyway).

Consider for example:

data WriteBackend = WriteBackend { write :: String -> IO (), close :: IO () }   getBackend :: String -> IO WriteBackend getBackend ":null:" = return (WriteBackend (const $ return ()) (return())) getBackend ":console:" = return $ WriteBackend putStrLn (return()) getBackend filename = do   h <- openFile filename WriteMode   return $ WriteBackend (hPutStrLn h) (hClose h) 

Which you could use like this:

greetBackend :: WriteBackend -> IO () greetBackend b = write b "Hello World!"  main = do   nullBackend <- getBackend ":null:"   consoleBackend <- getBackend ":console:"   fileBackend <- getBackend "file.txt"   greetBackend nullBackend -- does nothing   greetBackend consoleBackend -- writes to console   greetBackend fileBackend -- writes to file   close nullBackend -- does nothing   close consoleBackend -- does nothing   close fileBackend -- closes file 

Note that this is just if you genuinely need a dynamic approach - i.e. you don't know or care what the actual implementation is as long as it provides the right interface. If you have a limited number of cases you actually want to distinguish between, you should just use a sum type as @Daniel Wagner suggested.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: