
Hallo list, I don't quite get how ($!) works. I have this function: dbCreateIndices con dtn dof = do let dummy = newDbItem let query = "SELECT id FROM " ++ (dtn dummy) ++ " ORDER BY " ++ (dof dummy) ids <- liftM (map fromSql . concat ) $! quickQuery con query [] return $ IntMap.fromList $ zip ids [0..] quickQuery returns a lazy list of results, and I expected ($!) to make it strict. But my program crashes if I use it this way. But, if I add a print to the function: dbCreateIndices con dtn dof = do let dummy = newDbItem let query = "SELECT id FROM " ++ (dtn dummy) ++ " ORDER BY " ++ (dof dummy) ids <- liftM (map fromSql . concat ) $! quickQuery con query [] print ids return $ IntMap.fromList $ zip ids [0..] print makes the ids list strict and all is well. How is the correct way to use ($!) ? Cheers, -- -alex http://www.ventonegro.org/

Alex Queiroz wrote:
quickQuery returns a lazy list of results, and I expected ($!) to make it strict. But my program crashes if I use it this way. But, if I add a print to the function:
'Making things strict' isn't that simple. What $! does is force its arguments into WHNF before applying the function. WHNF means, roughly, either a lambda or a constructor at the top-level. In terms of your list (ids) you are forcing it to either (:) or [] at the top level (the two list constructors). So you force the 'top layer' of the list; you check if it's empty or not. If you want to force the whole spine of the list (that's the shape but not the values) then (length ids) `seq` return () would do that. However that's a bit clumsy. What kind of error are you seeing when you say 'it crashes'? It looks to me like your program creates a lazily generated list in the DB monad which looks superficially like a sensible thing to do. Jules

Hallo,
On 3/20/07, Jules Bean
What $! does is force its arguments into WHNF before applying the function. WHNF means, roughly, either a lambda or a constructor at the top-level.
I think I got it now.
However that's a bit clumsy. What kind of error are you seeing when you say 'it crashes'?
I must fetch all rows of the result, otherwise the database will be in an inconsistent state and all subsequent queries crash the application. Fetching all rows automatically finishes the previous statement, so I must evaluate the spine of the list. Cheers, -- -alex http://www.ventonegro.org/

Alex Queiroz wrote:
However that's a bit clumsy. What kind of error are you seeing when you say 'it crashes'?
I must fetch all rows of the result, otherwise the database will be in an inconsistent state and all subsequent queries crash the application. Fetching all rows automatically finishes the previous statement, so I must evaluate the spine of the list.
Odd. That sounds like a suprising library design to me. What's the point in returning a lazy list if you then leave everything in an inconsistent state until the list is fully evaluated? In that circumstance I'd rather have a strict list. I was intrigued and went and read the library documentation. It says "...Please note that the careless use of this function can lead to some unpleasant behavior...But then, similar caveats apply with hGetContents..." I don't know if I agree; these caveats seem definably worse than those which apply with hGetContents, if they can in fact cause crashes. The worse problem with hGetContents is that if someone else writes to that file while you're reading, you can see a mixture of old and new data. This seems to be far worse: you can't even make two queries unless you guarantee you finish the first completely first. Personally I'd inclined to suggest use of the strict FetchRow which the library provides than struggle with explicit uses of seq to fight an over-lazy IO action. Jules

Hallo,
On 3/20/07, Jules Bean
Personally I'd inclined to suggest use of the strict FetchRow which the library provides than struggle with explicit uses of seq to fight an over-lazy IO action.
Sounds like a good suggestion. Thanks very much for the help! Cheers, -- -alex http://www.ventonegro.org/

Hello,
dbCreateIndices con dtn dof = do let dummy = newDbItem let query = "SELECT id FROM " ++ (dtn dummy) ++ " ORDER BY " ++ (dof dummy) ids <- liftM (map fromSql . concat ) $! quickQuery con query [] return $ IntMap.fromList $ zip ids [0..]
quickQuery returns a lazy list of results, and I expected ($!) to make it strict. But my program crashes if I use it this way. But, if I add a print to the function:
When using lazy DB connections in HDBC (I'm assuming you're using HDBC since quickQuery is the pre-packaged lazy query call in HDBC), you have to be careful about forcing all the data your'e expecting from a query on a connection before using the same connection for something else. As the previous poster mentioned, $! will only evaluate the list to weak head normal form. Printing out the list will indeed force everything in the list to be evaluated. There is an extended discussion of this same issue (in the context of lazy file io) in this thread from haskell-cafe: http://www.haskell.org/pipermail/haskell-cafe/2007-March/023498.html -Jeff --- This e-mail may contain confidential and/or privileged information. If you are not the intended recipient (or have received this e-mail in error) please notify the sender immediately and destroy this e-mail. Any unauthorized copying, disclosure or distribution of the material in this e-mail is strictly forbidden.

Alex Queiroz wrote:
I don't quite get how ($!) works. I have this function:
ids <- liftM (map fromSql . concat ) $! quickQuery con query []
There's a difference between an IO action and the result of said action, and similarly there's a difference between making sure an action is evaluated and making sure the result of executing the action is evalulated. You did the former. If you really wanted to evaluate the result to WHNF (only) before finishing dbCreateIndices, this would work:
ids <- liftM (map fromSql . concat ) $ quickQuery con query [] ids `seq` return $ IntMap.fromList $ zip ids [0..]
But you probably need to evaluate the complete list, so you need more:
ids <- liftM (map fromSql . concat ) $ quickQuery con query [] foldr seq () ids `seq` return $ IntMap.fromList $ zip ids [0..]
Of course, if you insist on using ($!), that's also possible:
ids <- liftM (map fromSql . concat ) $ quickQuery con query [] return $ IntMap.fromList $ flip zip [0..] $! foldr seq ids ids
HTH. -Udo.
participants (4)
-
Alex Queiroz
-
Jeff Polakow
-
Jules Bean
-
Udo Stenzel