Search code examples
haskellgeneric-programmingtemplate-haskell

Convert to typed value from String


I have file with list of values and types, after reading them I need to put them into db. For that, I need to supply insertion function with properly typed tuple, so I'm trying to convert values with something like this

toProperType :: String -> String -> a  
toProperType tp val =
  case tp of
    "string" -> val            -- ::String
    "int"    -> toIntType val  -- ::Int64
    "bigint" -> toIntType val  -- ::Int64
    "integer"-> toIntType val    
    "utcdate"-> toDateType val -- :: UTCTime
    "double" -> toDoubleType val -- :: Double

Which is failing with

Couldn't match expected type ‘a’ with actual type ‘Double’ ‘a’ is a rigid type variable bound by

which I think is correct.

What is proper way to achieve this functionality?

Maybe I need some extension or generate separate functions with TH(but not sure how to dispatch them)


Solution

  • The issue here is the meaning of -> a in your function type. If you're function actually had this type, then whoever called your function should be able to specify a concrete type of their choosing (that you may not even have in scope) and then expect your function to work as if it had the type

    String -> String -> MyCustomType
    

    However this clearly isn't what you had in mind. You don't mean "for all types a, I have a function ...", you mean "For any two strings, there is some type a for which I have a value". This idea, that you get to choose the type variable instead of the caller, is called "existential quantification" and GHC does support it. However I don't really think that's what you want to do. After all, when you go to actually use this function, you'll probably want to be able to case on whether or not you got back a UTCTime or a Double or something. Since you cannot do this with existential quantification (just like how you cannot case on type variables in polymoprhic functions) we should instead create a custom data type:

    data Dyn = String String | Int Int | BigInt Integer | UTCDate UTCTime ...
    

    and so on. That is, you list out an explicit constructor for each case that your type may return and then your function will read

    toProperType :: String -> String -> Dyn  
    toProperType tp val =
      case tp of
        "string" -> String val            -- ::String
        "int"    -> Int $ toIntType val  -- ::Int64
        "bigint" -> BigInt $ toIntType val  -- ::Int64
        "integer"-> Integer $ toIntType val    
        "utcdate"-> UTCDate $ toDateType val -- :: UTCTime
        "double" -> Double $ toDoubleType val -- :: Double
    

    This is how serious Haskell libraries handle things like JSON parsing or what not so you're in good company. Now it's well typed and whoever calls this function just cases on the Dyn value and decides what to do based on the returned type.