在一个事务中执行许多查询

时间:2013-08-03 22:31:16

标签: haskell sqlite

我正在移植一个解析器+ SQLite数据导入器作为我在Haskell中的第一个项目,我需要一些帮助来了解一个人应该如何做monad的事情。 我正在使用parsec进行解析部分,然后使用转换函数来查找将数据插入数据库的SQLite查询。

现在,由于它使用了parsec解析器,结果数据的类型是:

IO (Either ParseError [(String, [SqlValue])])

这是数据样本

Right ("INSERT OR REPLACE INTO utterances 
(id,name,filelength,updated_at,checksum_algorithm, checksum) VALUES (ifnull(NULL, 
(select id from utterances where name = ?)) , ? ,?, datetime(),'MD5',?);",[SqlString 
"testdata/testdata.TextGrid",SqlString "testdata/testdata.TextGrid",SqlDouble 
1.0,SqlString "FAKE"])

现在,我需要在单个事务中运行查询,但是当数据在IO(E a x))包中时,我该怎么做?

就像我说的,我对Haskell很新,所以我很感谢能得到的所有帮助

1 个答案:

答案 0 :(得分:4)

monad的“技巧”是让每个操作尽可能深入计算。例如,假设我们有

result :: IO (Either ParseError [(String, [SqlValue])])

然后我们可以写

liftM go result

因此go只需要处理Either ParseError [(String, [SqlValue])]类型的内容,并且可以忽略IO。我们可以更进一步。

liftM (liftM makeQuery) result

将是我们可以放置makeQuery :: [(String, [SqlValue])] -> SqlQuery构建器的地方,该构建器创建我们要发送到数据库的查询。这是一个很容易自行测试的纯函数,比如在我们手工构建[(String, [SqlValue])]位的单元测试中。

我们可以通过展开在图层之间移动。每个monadic层都有自己的展开风格。例如,我们可以通过将其与Either

匹配来展开case
do either <- result   -- use do notation to look inside of the IO layer
   case either of
     Left parseError -> putStrLn (show parseError)     -- convert the error to some IO
     Right insides -> sendQuery db (makeQuery insides) -- send our built query

再次,通过进入Monad的内层,我们可以编写更简单的函数。例如,这里虚构的sendQuery具有类型DBConnection -> SqlQuery -> IO (),即它只发送SqlQueries但它们可能会到达。这具有非常强大的单一责任原则设计。

你为自己设定的目标还有很多复杂性,但处理monad基本上归结为这两个工具 - 分层工作并隔离你的行动。我尝试构建以下函数:(1)采用SQL数据库操作并将其包装在事务中(类型可能看起来像withTransaction :: IO () -> IO (),因为它会将IO操作升级为事务内部)( 2)构建像makeQuery这样的查询上面(3)处理错误,就像我使用show parseError一样,虽然这显然是一个hacky解决方案,并且(4)将你的解析隔离到一个地方。