我一直在关注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 ()
在视频中,三个函数的签名被重构,以便它们返回具有MonadIO
约束的泛型类型,即
readReservationsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m Int
saveReservation :: (MonadIO m) => ConnectionString -> Reservation -> m ()
据我所知,这样做会使函数更加灵活,因为它们不再依赖于具体的monad类型或特定的monad变换器堆栈配置。我也理解postReservation
函数仍然可以使用这些函数而不需要对其类型签名进行任何更改,因为它具有返回类型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 ())
视频的演示者继续制作名为postReservation
的{{1}}函数的具体版本,以消除类型类约束。编写postReservationIO
函数的损坏版本以证明它不能仅使用postReservationIO
函数,因为postReservation
函数返回的IO
类型不是实例postReservationIO
类型类。
然后告诉我们,为了消除MonadReader
函数中的MonadReader
约束,我们需要使用视频丢失的postReservationIO
函数。
大约在15:00,runReaderT
函数被重构为看起来像这样
postReservationIO
postReservationIO :: ReservationRendition -> IO (Httpresult ())
postReservationIO req = runReaderT (postReservation req) connStr
函数的类型签名为runReaderT
,我将其作为一个函数读取,该函数采用一些具体的ReaderT k r m a -> r -> m a
类型和一些ReaderT
类型的值(在我们的例子中是连接字符串),它会给你一些r
类型的monad。
在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
约束的吗?
答案 0 :(得分:6)
m
类型中的postReservation
被实例化为ReaderT * ConnectionString IO (HttpResult ())
,这是MonadReader ConnectionString
和MonadIO
的实例。
请注意ReaderT
仅通过runReaderT
明确提及。它的功能要求其论证是具体的ReaderT
而不是任意的MonadReader ConnectionString
。
修改:
正如@Benjamin Hodgson指出的那样,潜在的机制是返回类型多态,或更普遍的统一。
因此,当对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 ())
请阅读~
,因为'必须与'统一。例如,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)
并抱怨。