Newtype, Data.Text and instance declarations...

Hi all, There are a few things that I don't understand involving Data.Text and instance declarations... I'm building a small web app to practise Haskell and am using the SQLite.Simple library. I have defined a few types and functions like so: ''' data BookmarkDB = BookmarkDB Connection Table newtype Table = Table Text instance ToField Table where toField = SQLText initDB :: FilePath -> Table -> IO BookmarkDB initDB fp table = do let query = Query ("CREATE TABLE IF NOT EXISTS :table " <> " (url TEXT, tags TEXT, date INTEGER)") conn <- open fp executeNamed conn query [":table" := table] return $ BookmarkDB conn table ''' 1. My first question is about the instance declaration. Why would ''' instance ToField Table where toField = SQLText ''' return this error ? ''' Couldn't match type ‘Table’ with ‘Text’ Expected type: Table -> SQLData Actual type: Text -> SQLData • In the expression: SQLText In an equation for ‘toField’: toField = SQLText In the instance declaration for ‘ToField Table’ ''' I've noticed that if I write ''' instance ToField Table where toField (Table t) = SQLText t ''' I have no error and it compiles. I don't get it, what is 'toField' supposed to receive except a Table? 2. My second question is concerning the initDB function. If I write it that way, the code compiles, but I get an error during execution: bkmrk: SQLite3 returned ErrorError while attempting to perform prepare "CREATE TABLE IF NOT EXISTS :table (url TEXT, tags TEXT, date INTEGER)": near ":table": syntax error I don't understand what is wrong with the code and I doubt that my query is wrong because everything works with the last code snippet I wrote (see question #3). 3. Trying to get around the error I've noticed a few things. If I try to write this instead: ''' initDB :: FilePath -> Table -> IO BookmarkDB initDB fp table = do let query = Query ("CREATE TABLE IF NOT EXISTS " <> table <> " (url TEXT, tags TEXT, date INTEGER)") conn <- open fp execute_ conn query return $ BookmarkDB conn table ''' it doesn't compile because table is of type Table and not Data.Text.Internal.Text, which I get, but I don't know what is the Haskell way of resolving this. I've also noticed that this will help: ''' initDB :: FilePath -> Table -> IO BookmarkDB initDB fp t@(Table table) = do let query = Query ("CREATE TABLE IF NOT EXISTS " <> table <> " (url TEXT, tags TEXT, date INTEGER)") conn <- open fp execute_ conn query return $ BookmarkDB conn t ''' But I *really* don't get why! The most confusing part for me is that the last initDB function will do exactly what I want it to do, but I am unable to understand why it works...Why am I able to concatenate the 'table' now and not before and why is 't' of a different type than 'table'? All of this is very confusing and I find myself unable to reason about it. Sorry if this is a big/messy question. I hope everything is clear enough to answer... Thank you all!

On Fri, Apr 12, 2019 at 12:42 AM Guy-Laurent Subri
I've noticed that if I write ''' instance ToField Table where toField (Table t) = SQLText t '''
I have no error and it compiles. I don't get it, what is 'toField' supposed to receive except a Table?
Haskell does not automatically "convert" types. If something wants a Text, it will not accept a Table, even though that Table is just a wrapper around a Text. You must unwrap it to get the Text inside, which is what the (Table t) pattern is doing: matches a Table by its constructor, and gives you the contained Text bound to "t". This is somewhat more obvious when you have a type with multiple fields, or with multiple constructors, since pattern matching then lets you access all the fields, and you can have multiple patterns matching different constructors. data Foo = Foo Int Text someFoo (Foo i t) = -- do something with the Int i and Text t here data Foo = FooI Int | FooT Text someFoo (FooI i) = -- do something with the Int i here someFoo (FooT t) = -- do something with the Text t here (You can of course also combine them, multiple constructors each with zero or more fields.) 2. My second question is concerning the initDB function. If I write it
that way, the code compiles, but I get an error during execution:
bkmrk: SQLite3 returned ErrorError while attempting to perform prepare "CREATE TABLE IF NOT EXISTS :table (url TEXT, tags TEXT, date INTEGER)": near ":table": syntax error
I don't understand what is wrong with the code and I doubt that my query is wrong because everything works with the last code snippet I wrote (see question #3).
In general, you can't use that kind of substitution with SQL Data Definition Language (e.g. CREATE TABLE), nor is it generally usable to replace table or column names, only values. You need to concatenate the actual table name into the command, not a SQL string-like value nor an embedded SQL placeholder. 3. Trying to get around the error I've noticed a few things. If I try to
write this instead:
''' initDB :: FilePath -> Table -> IO BookmarkDB initDB fp table = do let query = Query ("CREATE TABLE IF NOT EXISTS " <> table <> " (url TEXT, tags TEXT, date INTEGER)") conn <- open fp execute_ conn query return $ BookmarkDB conn table '''
it doesn't compile because table is of type Table and not Data.Text.Internal.Text, which I get, but I don't know what is the Haskell way of resolving this.
Same as your first question: initDB fp t@(Table table) = do -- which unwraps the Table and gives you the Text within it as "table". But you also need the actual Table value, since you use it in the last line of your function, so you also need the as-pattern. -- brandon s allbery kf8nh allbery.b@gmail.com

Thank you, it makes perfect sense now. I don't know why I got confused with the pattern matching. Maybe it just looked like casting and I got the types wrong.
In general, you can't use that kind of substitution with SQL Data Definition Language (e.g. CREATE TABLE), nor is it generally usable to replace table or column names, only values. You need to concatenate the actual table name into the command, not a SQL string-like value nor an embedded SQL placeholder.
I didn't know that. Thank you. -- Guy-Laurent Subri
participants (2)
-
Brandon Allbery
-
Guy-Laurent Subri