1

我正在移植一个解析器 + SQLite 数据导入器作为我在 Haskell 中的第一个项目,我需要一些帮助来了解一个人应该如何做单子。我将 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 (Either xa)) 包中时,我该怎么做呢?

就像我说的,我对 Haskell 很陌生,所以我很感谢我能在这里得到的所有帮助

4

1 回答 1

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])]手动构建的单元测试中。

我们可以通过展开在层之间移动。每个单子层都有自己的展开方式。例如,我们可以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有 type DBConnection -> SqlQuery -> IO (),即它只是发送,SqlQueries但它们可能会到达。这具有很强的单一职责原则设计。

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

于 2013-08-04T03:51:01.277 回答