0

My Haskell skills are very much in it's infancy, and monads puzzle me.

However, I need to construct a function that will install all the fixtures in a sqlite3 database.

module UmeQuery where

import Database.HDBC 
import Database.HDBC.Sqlite3

testdb = "testdata/ume.umedb"

runSetup dbFile = do
    conn <- connectSqlite3 dbFile
    res <- withTransaction conn ( setup conn )
    disconnect conn
    return $ res

setup conn = do 
    n1 <- setupUtterances conn
    n2 <- setupLevels conn
    return $ [n1, n2] 


setupUtterances conn = do 
    q1 <- quickQuery' conn "DROP TABLE IF EXISTS utterances;" []
    q2 <- quickQuery' conn "CREATE TABLE utterances (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, filelength REAL, updated_at TEXT, checksum_algorithm TEXT, checksum TEXT, UNIQUE(name) ON CONFLICT FAIL );" []
    return $ [q1,q2] 

setupLevels conn = do 
    q1 <- quickQuery' conn "DROP TABLE IF EXISTS levels;"
    q2 <- quickQuery' conn "CREATE TABLE levels (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE ON CONFLICT FAIL );"
    return $ [q1,q2]

When I try to run this, i get the following output:

UmeQuery.hs:16:15:
Couldn't match expected type `IO [[[SqlValue]]]'
            with actual type `[SqlValue] -> [IO [[SqlValue]]]'
In the return type of a call of `setupLevels'
Probable cause: `setupLevels' is applied to too few arguments
In a stmt of a 'do' block: n2 <- setupLevels conn
In the expression:
  do { n1 <- setupUtterances conn;
       n2 <- setupLevels conn;
       return $ [n1, n2] }

What I would like to get is just something that indicates that everything is fine. Please note that I seem to be needing strictness, otherwise the "DROP TABLE " statements are not always exceeded before the new table is created.

Also, if I may ask two questions at once: setup is will eventually set up 12 tables in a similar fashion. Is there any way I can construct setup as an fmap of a ($ conn) over a list containing the involved functions in this case? It would make the code much nicer, of course.

David Young
  • 10,713
  • 2
  • 33
  • 47
Fredrik Karlsson
  • 485
  • 8
  • 21
  • You should *always* give your top-level bindings type signatures. It makes type errors much, *much* easier to figure out (this includes `main` by the way). – David Young Aug 02 '14 at 20:00
  • 1
    It looks like you aren't giving enough arguments to `quickQuery'` in `setupLevels`. The function type constructor `((->) a)` is also a monad, which is why the type checker is confused. It would be a lot to catch this kind of thing if you give those top-level bindings type signatures though. – David Young Aug 02 '14 at 20:06

1 Answers1

1

Sorry, yes the problem was ideed that i was currying without realizing it. Too late for coding, I guess.

import Database.HDBC 
import Database.HDBC.Sqlite3

testdb = "testdata/ume.umedb"

runSetup dbFile = do
    conn <- connectSqlite3 dbFile
    res <- withTransaction conn ( setup )
    disconnect conn
    return $ res

setup conn = do 
    n1 <- setupUtterances conn
    n2 <- setupLevels conn
    return $ n1 

setupUtterances conn = do 
    quickQuery' conn "DROP TABLE IF EXISTS utterances;" []
    quickQuery' conn "CREATE TABLE utterances (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, filelength REAL, updated_at TEXT, checksum_algorithm TEXT, checksum TEXT, UNIQUE(name) ON CONFLICT FAIL );" []
    return ()

setupLevels conn = do 
    quickQuery' conn "DROP TABLE IF EXISTS levels;" []
    quickQuery' conn "CREATE TABLE levels (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE ON CONFLICT FAIL );" []
    return ()

main = runSetup testdb

This works.

David Young
  • 10,713
  • 2
  • 33
  • 47
Fredrik Karlsson
  • 485
  • 8
  • 21
  • I know I already said this a couple times and not to harp on it *too* much, but it *really* does make *that* much of a difference in ease of understanding the type errors (and just general ease of understanding): I *highly* recommending adding type signatures to all of your top-level bindings. If you don't, one problem is that the error message in the type errors will be based on non-local information (you won't know if you made an error in the function you're calling or in the way you're calling it or both). It will save a *ton* of time. – David Young Aug 02 '14 at 22:58
  • Also, I'd personally suggest using spaces instead of tabs. In most languages, I wouldn't really say it's that big of an issue (even in Python), but things can line up in unintuitive ways in Haskell if you use tabs. You can do it if you're careful and mix tabs and spaces in just the right way, but you have to be very aware of it. I would recommend just setting your editor to convert all tabs to spaces automatically. – David Young Aug 02 '14 at 22:59
  • Yes, sorry. Now that I have figured out what they should be (which is still very confusing for me inside of IO) I have added type signatures for all these functions. I also discovered that my editor was not setup properly, so now everything is indented 4 spaces per default. Thank you for caring about my code! – Fredrik Karlsson Aug 03 '14 at 09:57