如何在Haskell中执行此重构?

时间:2016-10-28 16:42:10

标签: haskell

我正在使用servant-client生成的函数并尝试将它们包装在自定义monad中。我有servant-client生成的函数,其中包含以下签名:

getDomain
    :: AuthenticateReq (AuthProtect "x-api-key")
    -> Maybe Int
    -> Maybe Int
    -> Manager
    -> BaseUrl
    -> ExceptT ServantError IO Domain

及其后面的包装:

domain
  :: (MonadReader Env m, MonadIO m, MonadThrow m, MonadError ServantError m)
  => Int -> m (Domain)
domain domId = do
  auth <- mkAuth
  manager <- asks envManager
  url <- asks envBaseUrl
  baseUrl <- parseBaseUrl url
  r <-
    liftIO $ runExceptT $ getDomain auth (Just domId) Nothing manager baseUrl
  case r of
    Left e -> throwError e
    Right x -> return x

因为我将有许多这样的函数和包装器,我想提取一个辅助函数,它将:

  • 将服务生成的函数应用于除最后两个参数之外的所有参数
  • 使用MonadReader抓取管理器和baseUrl,将其提供给servant函数并将其完全返回

到目前为止,我的尝试如下:

helper
  :: (Monad m, MonadIO m, MonadReader Env m, MonadThrow m)
  => (Manager -> BaseUrl -> m b) -> m b
helper f =  do
  manager <- asks envManager
  url <- asks envBaseUrl
  baseUrl <- parseBaseUrl url
  f manager baseUrl

但不幸的是,由于以下错误,它无法编译:

Could not deduce (MonadReader Env IO)
  arising from a use of ‘helper’
from the context (MonadReader Env m,
                  MonadIO m,
                  MonadThrow m,
                  MonadError ServantError m)
  bound by the type signature for
             domain :: (MonadReader Env m, MonadIO m, MonadThrow m,
                        MonadError ServantError m) =>
                       Int -> m Domain

并且我不明白为什么编译器推断出MonadReader Env IO约束。我想知道为什么会发生这种情况以及如果可能的话,进行这种重构的方法是什么。我是否需要对domain函数本身进行任何更改以适应此更改?

<小时/> 的更新:

重构版domain如下:

domain
  :: (MonadReader Env m, MonadIO m, MonadThrow m, MonadError ServantError m)
  => Int -> m (Domain)
domain domId = do
  auth <- mkAuth
  r <-
    liftIO $ runExceptT $ helper $ getDomain auth (Just domId) Nothing
  case r of
    Left e -> throwError e
    Right x -> return x

更新2:

我现在已经找到了一个解决方案:

  • 似乎可以编译和工作
  • 我承认我并不理解
domain
  :: (MonadReader Env m, MonadIO m, MonadThrow m, MonadError ServantError m)
  => Int -> m (Domain)
domain domId = do
  auth <- mkAuth
  helper $ getDomain auth (Just domId) Nothing

helper
  :: (MonadReader Env m, MonadIO m, MonadError e m)
  => (Manager -> BaseUrl -> ExceptT e IO b) -> m b
helper f = do
  env <- ask
  r <- liftIO $ runExceptT $ runReaderT (helper1 f) env
  case r of
    Left e -> throwError e
    Right x -> return x

-- based on
-- https://github.com/passy/giphy-api/blob/4b006db2fe566b7200f6c81b8c56cabe2a595b90/src/Web/Giphy.hs#L359
helper1
  :: (Monad m, MonadTrans t, MonadThrow (t m), MonadReader Env (t m))
  => (Manager -> BaseUrl -> m b) -> t m b
helper1 f = do
  manager <- asks envManager
  url <- asks envBaseUrl
  baseUrl <- parseBaseUrl url
  lift $ f manager baseUrl

helperhelper1合并到一个函数中也是一件好事,到目前为止我还没有成功。我仍然不清楚原始问题,但我认为它与getDomain明确使用IO作为基本monad有关。

根据本杰明的建议删除liftIO $ runExceptTcase表达式

domain
  :: (MonadReader Env m, MonadIO m, MonadThrow m, MonadError ServantError m)
  => Int -> m (Domain)
domain domId = do
  auth <- mkAuth
  helper $ getDomain auth (Just domId) Nothing

helper
  :: (Monad m, MonadIO m, MonadReader Env m, MonadThrow m)
  => (Manager -> BaseUrl -> m b) -> m b
helper f =  do
  manager <- asks envManager
  url <- asks envBaseUrl
  baseUrl <- parseBaseUrl url
  f manager baseUrl

导致以下错误:

Couldn't match type ‘m’ with ‘ExceptT ServantError IO’
  ‘m’ is a rigid type variable bound by
      the type signature for
        domain :: (MonadReader Env m, MonadIO m, MonadThrow m,
                    MonadError ServantError m) =>
                   Int -> m Domain
      at <path>
Expected type: m Domain
  Actual type: ExceptT ServantError IO Domain
Relevant bindings include
  domain :: Int -> m Domain
    (bound at <path>)
In a stmt of a 'do' block:
  helper $ getDomain auth (Just domId) Nothing
In the expression:
  do { auth <- mkAuth;
       helper $ getDomain auth (Just domId) Nothing }
In an equation for ‘domain’:
    domain domId
      = do { auth <- mkAuth;
             helper $ getDomain auth (Just domId) Nothing }

即使使用类型孔,也会指示返回值为正确类型:

domain
  :: (MonadReader Env m, MonadIO m, MonadThrow m, MonadError ServantError m)
  => Int -> m (Domain)
domain domId = do
  auth <- mkAuth
  x <- helper $ getDomain auth (Just domId) Nothing
  return _

helper
  :: (Monad m, MonadIO m, MonadReader Env m, MonadThrow m)
  => (Manager -> BaseUrl -> m b) -> m b
helper f =  do
  manager <- asks envManager
  url <- asks envBaseUrl
  baseUrl <- parseBaseUrl url
  f manager baseUrl

错误:

Found hole ‘_’ with type: Domain
Relevant bindings include
  x :: Domain (bound at <path>)
  auth :: AuthenticateReq (AuthProtect "x-api-key")
    (bound at <path>)
  domId :: Int (bound at <path>)
  domain :: Int -> m Domain
    (bound at <path>)
In the first argument of ‘return’, namely ‘_’
In a stmt of a 'do' block: return _
In the expression:
  do { auth <- mkAuth;
       x <- helper $ getDomain auth (Just domId) Nothing;
       return _ }

0 个答案:

没有答案