Haskell - 在API中公开IO操作

时间:2015-01-09 14:48:21

标签: api haskell monads

我写了一个小型库[1],它与一个包含600多个西班牙语动词的postgresql DB接口,并提取出结合和其他有用的东西。

我有一个执行数据库读取的功能。它看起来像这样(我正在使用postgresql-simple [2]库):

-- | A postgres query.
queryDB              :: (ToRow params, FromRow a) => Query -> params -> IO [a]
queryDB q paramTypes =  do
    c      <-  connection
    return =<< query c q paramTypes

我在库中公开的每个函数都使用此函数并返回某种类型的IO操作。例如,如果用户结合动词&#39; ser&#39;使用conjugate,我得到了IO [Conjugation]

-- | Conjugate the verb 'i' in the tense 't' and mood 'm'.
-- 
-- > conjugate "ser" "Presente" "Indicativo"
conjugate       :: Infinitive -> Tense -> Mood -> IO [Conjugation]
conjugate i t m =  queryDB conjugationQuery [i :: Infinitive, 
                                             t :: Tense, 
                                             m :: Mood]

我是在Haskell中编写库的新手。将conjugate等函数保留为导出IO操作可以吗?它们确实与数据库进行交互,但这并不是函数的重点......用户只需要进行变换。通常情况下,如果我用其他语言编写这样的代码,用户就不会知道发生了IO操作。

我可以分离IO并公开纯函数吗?

2 个答案:

答案 0 :(得分:2)

由于您正在访问数据库,所以没有。 Haskell的一个重要部分是指定使用您的API的人正在执行IO操作。由于IO操作可能会失败,为同一输入返回不同的结果,或者发射导弹,我们总是在发生这种情况时告诉用户。

如果我使用您的API但没有您的数据库会怎么样?然后我可能会看到一些关于没有连接的错误消息。或者,如果我确实拥有了您的数据库但修改了它以返回不正确的变形,那么您无法保证conjugate将始终返回给定特定不定式,紧张和情绪的相同变形。这意味着您的conjugate功能不可能是纯粹的。

答案 1 :(得分:2)

如果您想避免为每个查询重新连接到数据库,您可以做的一件事就是在newtype上创建ReaderT Connection IO包装器,然后在整个地方使用,然后提供单独的runDB {1}}功能:

newtype DB a = MkDB{ unDB :: ReaderT DBConnection IO a } deriving (Functor, Applicative, Monad)

queryDB  :: (ToRow params, FromRow a) => Query -> params -> DB [a]
queryDB q paramTypes =  MkDB $ do
    c <- ask
    lift $ query c q paramTypes

conjugate :: Infinitive -> Tense -> Mood -> DB [Conjugation]
conjugate i t m =  queryDB conjugationQuery [i :: Infinitive, 
                                             t :: Tense, 
                                             m :: Mood]

-- Of course, this still needs to be in IO
runDB :: DB a -> IO a
runDB db = runReaderT db =<< connection

关键是导出MkDBunDB; DB是一种opaque类型,用户只能通过导出的函数(conjugate等)和monadic组合器使用。这样,未经稀释的IO不会遍布客户端代码。