【问题标题】:Eliminating MonadReader constraint using runReaderT使用 runReaderT 消除 MonadReader 约束
【发布时间】:2017-11-20 21:52:41
【问题描述】:

我一直在看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 函数的签名也更新为包含MonadIOMonadReader 约束,即

postReservation :: (MonadReader ConnectionString m, MonadIO m) => ReservationRendition -> m (HttpResult ())

视频的演示者继续制作了名为postReservationIOpostReservation 函数的具体版本,以消除类型类约束。编写了postReservationIO 函数的损坏版本以证明它不能仅使用postReservation 函数,因为postReservationIO 函数返回的IO 类型不是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 约束?

【问题讨论】:

    标签: haskell typeclass monad-transformers


    【解决方案1】:

    postReservations 类型中的m 被实例化为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 ())
    

    ~ 读作“必须统一”。例如,runReaderT 的第二个参数是 ConnectionString 的事实需要类型变量 rConnectionString 统一。

    约束ReaderT k r m a ~ (MonadReader ConnectionString m', MonadIO m') => m' (HttpResult ()) 是我之前提到的。这就是将m' 实例化为ReaderT * ConnectionString m 的内容,由于最后一个约束,它进一步实例化为ReaderT * ConnectionString IO

    只有在满足所有类型变量约束之后,GHC 才会检查 ReaderT * ConnectionString IO 是否满足 MonadReader ConnectionStringMonadIO,确实如此。

    如果不是这样,例如当postReservation :: (MonadLogger m, MonadIO m) => ReservationRendition -> m (HttpResult ()) 时,编译器将找不到实例MonadLogger (ReaderT * ConnectionString IO) 并抱怨。

    【讨论】:

    • 好的答案,如果讨论返回类型多态性的工作原理会更好:)
    • 当您说postReservations 中的m 类型被实例化为ReaderT * ConnectionString IO (HttpResult ()) 时,这仅仅是MonadReader 实例的一个特化ReaderTMonad m => MonadReader r (ReaderT * r m)
    • 没错。在我们的例子中,m ~ IO.
    • 知道了。感谢您的帮助
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-03
    • 2021-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多