使用 runReaderT 消除 MonadReader 约束
Eliminating MonadReader constraint using runReaderT
我一直在关注 Refactoring some Haskell code to use MTL,它重构了一些 Haskell 代码以利用 mtl 包中的类型类。
该代码包含具有以下签名的 postReservation
函数:
postReservation :: ReservationRendition -> IO (HttpResult ())
postReservation
函数的实现使用了具有以下签名的三个附加函数:
readReservationsFromDB :: ConnectionString -> ZonedTime -> IO [Reservation]
getReservedSeatsFromDB :: ConnectionString -> ZonedTime -> IO Int
saveReservation :: ConnectionString -> Reservation -> IO ()
在视频中,重构了三个函数的签名,以便它们 return 具有 MonadIO
约束的通用类型,即
readReservationsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m Int
saveReservation :: (MonadIO m) => ConnectionString -> Reservation -> m ()
我知道这样做会使函数更加灵活,因为它们不再依赖于具体的 monad 类型或特定的 monad 转换器堆栈配置。我还了解到 postReservation
函数仍然可以使用这些函数而无需对其类型签名进行任何更改,因为它具有 return 类型的 IO,它是 MonadIO 类型类的一个实例。
接下来,这三个函数被重构为包含一个 MonadReader
约束,这样就不需要显式传递连接字符串,即
readReservationsFromDB :: (MonadReader ConnectionString m, MonadIO m) => ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadReader ConnectionString m, MonadIO m) => ZonedTime -> m Int
saveReservation :: (MonadReader ConnectionString m, MonadIO m) => Reservation -> m ()
postReservation
函数的签名也更新为包含 MonadIO
和 MonadReader
约束,即
postReservation :: (MonadReader ConnectionString m, MonadIO m) => ReservationRendition -> m (HttpResult ())
视频的演示者继续制作名为 postReservationIO
的 postReservation
函数的具体版本,以消除类型类约束。 postReservationIO
函数的损坏版本是为了证明它不能只使用 postReservation
函数,因为 IO
类型 return 由 postReservationIO
编辑函数不是 MonadReader
类型类的实例。
然后我们被告知,为了从 postReservationIO
函数中消除 MonadReader
约束,我们需要使用 runReaderT
函数,这就是视频丢失我的地方。
大约 15:00,postReservationIO
函数重构为如下所示
postReservationIO :: ReservationRendition -> IO (Httpresult ())
postReservationIO req = runReaderT (postReservation req) connStr
runReaderT
函数具有 ReaderT k r m a -> r -> m a
的类型签名,我正在阅读它作为一个函数,它采用一些具体的 ReaderT
类型和一些 r
类型的值(在我们的例子中是连接字符串)它会返回一些 m a
.
类型的 monad
在 postReservationIO
实现中,我们将 (postReservation req)
作为第一个参数传递给 runReaderT
函数。 (postReservation req)
具有类型
(MonadReader ConnectionString m, MonadIO m) => m (HttpResult ())
据我所知这不是 ReaderT
所以我很难理解它是如何工作的。
谁能解释一下我们如何从 (MonadReader ConnectionString m, MonadIO m) => m (HttpResult ())
类型跳转到 ReaderT k r m a
以消除 MonadReader
约束?
postReservation
s 类型中的 m
实例化为 ReaderT * ConnectionString IO (HttpResult ())
,它是 MonadReader ConnectionString
和 MonadIO
的实例。
请注意 ReaderT
仅通过 runReaderT
明确提及。正是这个函数要求它的参数是具体的 ReaderT
而不是任意的 MonadReader ConnectionString
.
编辑:
正如@Benjamin Hodgson 指出的那样,底层机制是 return 类型多态性,或更普遍的统一。
所以,当对 postReservationIO
的主体进行类型检查时,大致会发生以下情况:
-- What we know, because we already type-checked them (this is necessary information about free variables):
runReaderT :: ReaderT k r m a -> r -> m a
postReservation req :: (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ())
connStr :: ConnectionString
-- What we want to check
runReaderT (postReservation req) connStr :: IO (HttpResult ())
-- Unifying `runReaderT` with its arguments results in the following constraints:
-- First argument
ReaderT k r m a ~ (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ())
-- Second argument
r ~ ConnectionString
-- Return type
m (HttpResult ()) ~ IO (HttpResult ())
读 ~
为 'has to unify with'。例如,runReaderT
的第二个参数是 ConnectionString
的事实需要类型变量 r
与 ConnectionString
.
统一
约束 ReaderT k r m a ~ (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ())
是我之前提到的。这就是将 m'
实例化为 ReaderT * ConnectionString m
,作为最后一个约束的结果进一步实例化为 ReaderT * ConnectionString IO
。
只有在满足所有类型变量约束后,GHC 才会检查 ReaderT * ConnectionString IO
是否满足 MonadReader ConnectionString
和 MonadIO
,确实如此。
如果情况并非如此,例如当 postReservation :: (MonadLogger m, MonadIO m) => ReservationRendition -> m (HttpResult ())
时,编译器将无法找到实例 MonadLogger (ReaderT * ConnectionString IO)
并报错。
我一直在关注 Refactoring some Haskell code to use MTL,它重构了一些 Haskell 代码以利用 mtl 包中的类型类。
该代码包含具有以下签名的 postReservation
函数:
postReservation :: ReservationRendition -> IO (HttpResult ())
postReservation
函数的实现使用了具有以下签名的三个附加函数:
readReservationsFromDB :: ConnectionString -> ZonedTime -> IO [Reservation]
getReservedSeatsFromDB :: ConnectionString -> ZonedTime -> IO Int
saveReservation :: ConnectionString -> Reservation -> IO ()
在视频中,重构了三个函数的签名,以便它们 return 具有 MonadIO
约束的通用类型,即
readReservationsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m Int
saveReservation :: (MonadIO m) => ConnectionString -> Reservation -> m ()
我知道这样做会使函数更加灵活,因为它们不再依赖于具体的 monad 类型或特定的 monad 转换器堆栈配置。我还了解到 postReservation
函数仍然可以使用这些函数而无需对其类型签名进行任何更改,因为它具有 return 类型的 IO,它是 MonadIO 类型类的一个实例。
接下来,这三个函数被重构为包含一个 MonadReader
约束,这样就不需要显式传递连接字符串,即
readReservationsFromDB :: (MonadReader ConnectionString m, MonadIO m) => ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadReader ConnectionString m, MonadIO m) => ZonedTime -> m Int
saveReservation :: (MonadReader ConnectionString m, MonadIO m) => Reservation -> m ()
postReservation
函数的签名也更新为包含 MonadIO
和 MonadReader
约束,即
postReservation :: (MonadReader ConnectionString m, MonadIO m) => ReservationRendition -> m (HttpResult ())
视频的演示者继续制作名为 postReservationIO
的 postReservation
函数的具体版本,以消除类型类约束。 postReservationIO
函数的损坏版本是为了证明它不能只使用 postReservation
函数,因为 IO
类型 return 由 postReservationIO
编辑函数不是 MonadReader
类型类的实例。
然后我们被告知,为了从 postReservationIO
函数中消除 MonadReader
约束,我们需要使用 runReaderT
函数,这就是视频丢失我的地方。
大约 15:00,postReservationIO
函数重构为如下所示
postReservationIO :: ReservationRendition -> IO (Httpresult ())
postReservationIO req = runReaderT (postReservation req) connStr
runReaderT
函数具有 ReaderT k r m a -> r -> m a
的类型签名,我正在阅读它作为一个函数,它采用一些具体的 ReaderT
类型和一些 r
类型的值(在我们的例子中是连接字符串)它会返回一些 m a
.
在 postReservationIO
实现中,我们将 (postReservation req)
作为第一个参数传递给 runReaderT
函数。 (postReservation req)
具有类型
(MonadReader ConnectionString m, MonadIO m) => m (HttpResult ())
据我所知这不是 ReaderT
所以我很难理解它是如何工作的。
谁能解释一下我们如何从 (MonadReader ConnectionString m, MonadIO m) => m (HttpResult ())
类型跳转到 ReaderT k r m a
以消除 MonadReader
约束?
postReservation
s 类型中的 m
实例化为 ReaderT * ConnectionString IO (HttpResult ())
,它是 MonadReader ConnectionString
和 MonadIO
的实例。
请注意 ReaderT
仅通过 runReaderT
明确提及。正是这个函数要求它的参数是具体的 ReaderT
而不是任意的 MonadReader ConnectionString
.
编辑:
正如@Benjamin Hodgson 指出的那样,底层机制是 return 类型多态性,或更普遍的统一。
所以,当对 postReservationIO
的主体进行类型检查时,大致会发生以下情况:
-- What we know, because we already type-checked them (this is necessary information about free variables):
runReaderT :: ReaderT k r m a -> r -> m a
postReservation req :: (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ())
connStr :: ConnectionString
-- What we want to check
runReaderT (postReservation req) connStr :: IO (HttpResult ())
-- Unifying `runReaderT` with its arguments results in the following constraints:
-- First argument
ReaderT k r m a ~ (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ())
-- Second argument
r ~ ConnectionString
-- Return type
m (HttpResult ()) ~ IO (HttpResult ())
读 ~
为 'has to unify with'。例如,runReaderT
的第二个参数是 ConnectionString
的事实需要类型变量 r
与 ConnectionString
.
约束 ReaderT k r m a ~ (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ())
是我之前提到的。这就是将 m'
实例化为 ReaderT * ConnectionString m
,作为最后一个约束的结果进一步实例化为 ReaderT * ConnectionString IO
。
只有在满足所有类型变量约束后,GHC 才会检查 ReaderT * ConnectionString IO
是否满足 MonadReader ConnectionString
和 MonadIO
,确实如此。
如果情况并非如此,例如当 postReservation :: (MonadLogger m, MonadIO m) => ReservationRendition -> m (HttpResult ())
时,编译器将无法找到实例 MonadLogger (ReaderT * ConnectionString IO)
并报错。