1

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)

Filip van Hoft
  • 301
  • 1
  • 7
  • 2
    well as the compiler tells you your function is really *generic* - it claims that it can return any type `a` but then you go on and your cases return very specific types ... so the compiler complains. The first step would be to try and find out what the results should have in common - here you want to be able to put them into a database of some sort (you did not write anything about) - so my first try would be to return a function that given a db-context will write the value into it: `makePersistance :: String -> String -> (DbContext -> IO ())` – Random Dev Mar 25 '16 at 12:57
  • 1
    btw: it's probably enough to return a *SQL*-Statement that represent the `INSERT` into your database (assuming it's some kind of SQL-DB) - so it could be `String -> String -> String` as well and maybe this is easier for you to see - like `case tp of "string" -> "INSERT INTO table VALUES ('" ++ val ++ "')"` (of course I feel I have to tell you that this might be really dangerous as it opens the door for SQL-inject attacs) – Random Dev Mar 25 '16 at 12:59
  • I have PostgreSQL and trying to insert with something like `res :: (Either SomeException [Only Int]) <- try $ query conn query' (a1::String, a2::String, i3::Int64, a4::String, a5::String, a6::String, i7::Int64, a8::String, a9::String, a10::String, a11::String, a12::String, a13::String, t14::UTCTime, a15::String)` and combination of types can be different, I want this tuple to be properly typed insted of writing by hand – Filip van Hoft Mar 25 '16 at 13:01
  • 1
    use the first idea I gave you where you replace `DbContext -> IO()` which whatever type your `query'` will have – Random Dev Mar 25 '16 at 13:03
  • 2
    You could make a `data MyValue = IntVal Int64 | StrVal String | FloVal Double | TimeVal UTVTime` and return that. Your insertion function can then pattern match on the type. –  Mar 25 '16 at 13:11
  • Are you using `postgreSQL-simple`? If so, you may want your function to produce a `Query`, rather than `IO`. But the general pattern Carsten suggests is a solid one. – dfeuer Mar 25 '16 at 16:26

1 Answers1

2

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.

daniel gratzer
  • 52,833
  • 11
  • 94
  • 134