Ben*_*ale 3 haskell typeclass monad-transformers
我一直在观察重构一些Haskell代码以使用MTL,它重构了一些Haskell代码以利用mtl包中的类型类.
该代码包含postReservation具有以下签名的函数:
postReservation :: ReservationRendition -> IO (HttpResult ())
Run Code Online (Sandbox Code Playgroud)
该postReservation函数的实现使用具有以下签名的三个附加函数:
readReservationsFromDB :: ConnectionString -> ZonedTime -> IO [Reservation]
getReservedSeatsFromDB :: ConnectionString -> ZonedTime -> IO Int
saveReservation :: ConnectionString -> Reservation -> IO ()
Run Code Online (Sandbox Code Playgroud)
在视频中,三个函数的签名被重构,以便它们返回具有MonadIO约束的泛型类型,即
readReservationsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m [Reservation]
getReservedSeatsFromDB :: (MonadIO m) => ConnectionString -> ZonedTime -> m Int
saveReservation :: (MonadIO m) => ConnectionString -> Reservation -> m ()
Run Code Online (Sandbox Code Playgroud)
我知道这样做会使函数更加灵活,因为它们不再依赖于具体的monad类型或特定的monad变换器堆栈配置.我也理解该postReservation函数仍然可以使用这些函数而不需要对其类型签名进行任何更改,因为它具有返回类型IO,它是MonadIO类型类的实例.
接下来,重构三个函数以包括MonadReader约束,以便不需要显式传递连接字符串即ie
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 ()
Run Code Online (Sandbox Code Playgroud)
postReservation函数的签名也被更新以包括MonadIO和MonadReader约束即
postReservation :: (MonadReader ConnectionString m, MonadIO m) => ReservationRendition -> m (HttpResult ())
Run Code Online (Sandbox Code Playgroud)
视频的演示者继续创建postReservation调用函数的具体版本,postReservationIO以消除类型类约束.postReservationIO编写该函数的破坏版本以证明它不能仅仅使用该postReservation函数,因为函数IO返回的类型postReservationIO不是MonadReader类型类的实例.
然后我们被告知,为了消除函数的MonadReader约束,postReservationIO我们需要利用runReaderT视频失去我的功能.
在大约15:00,postReservationIO函数被重构为这样
postReservationIO :: ReservationRendition -> IO (Httpresult ())
postReservationIO req = runReaderT (postReservation req) connStr
Run Code Online (Sandbox Code Playgroud)
该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 ())
Run Code Online (Sandbox Code Playgroud)
据我所知,并非ReaderT如此,我很难理解这是如何运作的.
谁能解释我们是如何做的类型的东西跳跃(MonadReader ConnectionString m, MonadIO m) => m (HttpResult ())到ReaderT k r m a以消除MonadReader约束?
将m在postReservationS型被实例化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 ())
Run Code Online (Sandbox Code Playgroud)
读~为'必须与'统一'.例如,第二个参数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)并抱怨.