我写了一个小型库[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并公开纯函数吗?
答案 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
关键是不导出MkDB
和unDB
; DB
是一种opaque类型,用户只能通过导出的函数(conjugate
等)和monadic组合器使用。这样,未经稀释的IO不会遍布客户端代码。