Turbinado has the beginnings of a database ORM built in. I’m hoping to make it super simple to interact with a relational DB, but I’m bumping up again import cycles. Ticket about import cycles here.

The Problem

import qualified App.Models.PageModel as Page import qualified App.Models.CommentModel as Comment someFn = do p <- Page.find "tutorial" -- find using a primary key of "tutorial" cs <- Page.findAllChildComment p let c = head cs p2 <- Comment.getParentPage c

Yeah, could be prettier (hmm… maybe Rails’ pluralization is good after all…), but that’s a pretty nice way to access related Pages and Comments in a SQL database. What _doesn’t_ work, though are the imports. Since Page.findAllChildComment returns Comments, PageModel needs to import CommentModel. But, since CommentModel also uses the Page type, CommentModel needs to import PageModel. And thus the cycle begins…

Fix #1

The custom seems to be to push the data types out to a Types.hs file.

Assuming that Types.hs is imported and exported by all Models, then the following doesn’t work:



import qualified App.Models.PageModel as PageModel import qualified App.Models.CommentModel as CommentModel someFn = do let p = Page {title = "blah", content = "ack", _id = "page1"} PageModel.insert p False

Is the Page type the one from PageModel or from CommentModel? Both models export the Page type.



Update:

As Twan points out in the Comments, the above would work if I were happy to do either of:

let p = PageModel.Page {title = "blah", content = "ack", _id = "page1"} let p = CommentModel.Page {title = "blah", content = "ack", _id = "page1"}

Perhaps it’s unreasonable of me, but I want to be able to refer to the type without being forced to refer to the module. Besides, this post is really a gripe about import cycles, so I can’t have Fix #1 work and still gripe…

Fix #2

Push the data types out to a Types.hs file, but don’t export Types from the Models. Now you have to explicitly import Types whenever you’re using a Model:

import App.Models.Types -- I'm not pretty... import qualified App.Models.PageModel as PageModel import qualified App.Models.CommentModel as CommentModel someFn = do let p = Page {title = "blah", content = "ack", _id = "page1"} PageModel.insert p False

Now Page is clearly taken from App.Models.Types. But my delicate aesthetic sensibilities are offended.

But wait! We’re still broken! Since PageModel and CommentModel may use functions from each other, import cycles can still be produced. For example, Page.findAllChildComment uses a function from CommentModel:



instance HasChildComment (Page) where findAllChildComment p = CommentModel.findAllWhere "page_id = ?" [HDBC.toSql $ _id p]

Now if CommentModel needs a function from PageModel, then an import cycle is formed. So for the ORM to work relations in the DB can only form an acyclic graph. That’s bad.

Fix #3

Even though PageModel depends on CommentModel, don’t import it. Just duplicate the CommentModel code into PageModel. Ugly:



import App.Models.Types instance HasChildComment (Page) where findAllChildComment p = do conn <- getEnvironment >>= (return . fromJust . getDatabase ) res <- liftIO $ HDBC.handleSqlError $ HDBC.quickQuery' conn ("SELECT _id , commenter , page_id , post FROM comment WHERE (page_id = ?) ") [_id p] return $ map (\r -> Comment (HDBC.fromSql (r !! 0)) (HDBC.fromSql (r !! 1)) (HDBC.fromSql (r !! 2)) (HDBC.fromSql (r !! 3))) res

I’m Sad

Haskell is such a lovely language, but my workaround for my module import cycles is pretty hideous. Am I missing something?