使用runReaderT消除MonadReader约束

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函数的签名也被更新以包括MonadIOMonadReader约束即

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约束?

Seb*_*raf 6

mpostReservationS型被实例化ReaderT * ConnectionString IO (HttpResult ()),这既是一个实例MonadReader ConnectionStringMonadIO.

请注意,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)并抱怨.

  • 很好的答案,通过讨论返回类型多态如何工作会更好:) (2认同)