我正在移植一个解析器+ 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很新,所以我很感谢能得到的所有帮助
答案 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)将你的解析隔离到一个地方。