Haskell - 在 API 中公开 IO 操作
Haskell - Exposing IO actions in API
我写了一个小型库[1],它与包含 600 多个西班牙语动词的 postgresql 数据库接口,并提取了变位和其他有用的东西。
我只有一个函数可以执行数据库读取。它看起来像这样(我正在使用 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
我在库中公开的每个函数都使用此函数和returns某种类型的 IO 操作。例如,如果用户使用 conjugate
结合动词 'ser',我会返回 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 并公开纯函数吗?
因为您要访问数据库,所以不。 Haskell 的很大一部分是向使用您的 API 的人指定他们正在执行 IO 操作。由于 IO 操作可能会失败,return 相同输入的不同结果,或者发射导弹,我们总是在发生这种情况时告诉用户。
如果我使用了您的 API 但没有您的数据库会怎样?然后我可能会看到某种关于没有连接的错误消息。或者,如果我确实有你的数据库,但将其修改为 return 不正确的变位,那么你不能保证 conjugate
将始终 return 给定特定不定式、时态和语气的相同变位.这意味着您的 conjugate
函数不能是纯函数。
如果你想避免为每个查询重新连接到数据库,你可以做的一件事是在你到处使用的 ReaderT Connection IO
上做一个 newtype
包装器,然后提供一个单独的 runDB
函数:
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
是一种不透明类型,用户只能通过导出函数(conjugate
等)和单子组合器使用。这样,未稀释的 IO 就不会遍布客户端代码。
我写了一个小型库[1],它与包含 600 多个西班牙语动词的 postgresql 数据库接口,并提取了变位和其他有用的东西。
我只有一个函数可以执行数据库读取。它看起来像这样(我正在使用 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
我在库中公开的每个函数都使用此函数和returns某种类型的 IO 操作。例如,如果用户使用 conjugate
结合动词 'ser',我会返回 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 并公开纯函数吗?
因为您要访问数据库,所以不。 Haskell 的很大一部分是向使用您的 API 的人指定他们正在执行 IO 操作。由于 IO 操作可能会失败,return 相同输入的不同结果,或者发射导弹,我们总是在发生这种情况时告诉用户。
如果我使用了您的 API 但没有您的数据库会怎样?然后我可能会看到某种关于没有连接的错误消息。或者,如果我确实有你的数据库,但将其修改为 return 不正确的变位,那么你不能保证 conjugate
将始终 return 给定特定不定式、时态和语气的相同变位.这意味着您的 conjugate
函数不能是纯函数。
如果你想避免为每个查询重新连接到数据库,你可以做的一件事是在你到处使用的 ReaderT Connection IO
上做一个 newtype
包装器,然后提供一个单独的 runDB
函数:
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
是一种不透明类型,用户只能通过导出函数(conjugate
等)和单子组合器使用。这样,未稀释的 IO 就不会遍布客户端代码。