4

我正在为Snap web 框架编写一个新的身份验证系统,因为内置的身份验证系统不够模块化,并且它具有一些对我的应用程序来说是冗余/“自重”的功能。不过,这个问题根本与 Snap 无关。

这样做时,我遇到了类型约束不明确的问题。在下面的代码中,对我来说很明显的类型back只能是b函数类型中的类型变量,但 GHC 抱怨类型不明确。

如何在不使用 eg的情况下更改以下代码,使类型为backis (因为问题出在约束上,而不是类型太笼统)?某处是否需要功能依赖?bScopedTypeVariables

相关类型类:

data AuthSnaplet b u =
  AuthSnaplet
  { _backend    :: b
  , _activeUser :: Maybe u
  }
-- data-lens-template:Data.Lens.Template.makeLens
-- data-lens:Data.Lens.Common.Lens
-- generates: backend :: Lens (AuthSnaplet b u) b
makeLens ''AuthSnaplet

-- Some encrypted password
newtype Password =
  Password
  { passwordData :: ByteString
  }

-- data-default:Data.Default.Default
class Default u => AuthUser u where
  userLogin :: Lens u Text
  userPassword :: Lens u Password

class AuthUser u => AuthBackend b u where
  save :: MonadIO m => b -> u -> m u
  lookupByLogin :: MonadIO m => b -> Text -> m (Maybe u)
  destroy :: MonadIO m => b -> u -> m ()

-- snap:Snap.Snaplet.Snaplet
class AuthBackend b u => HasAuth s b u where
  authSnaplet :: Lens s (Snaplet (AuthSnaplet b u))

失败的代码:

-- snap:Snap.Snaplet.with :: Lens v (Snaplet v') -> m b v' a -> m b v a
-- data-lens-fd:Data.Lens.access :: MonadState a m => Lens a b -> m b
loginUser :: HasAuth s b u
          => Text -> Text -> Handler a s (Either AuthFailure u)
loginUser uname passwd = with authSnaplet $ do
  back <- access backend
  maybeUser <- lookupByLogin back uname -- !!! type of back is ambiguous !!!
  -- ... For simplicity's sake, let's say the function ends like this:
  return . Right . fromJust $ maybeUser

完整错误:

src/Snap/Snaplet/Authentication.hs:105:31:
    Ambiguous type variables `b0', `u0' in the constraint:
      (HasAuth s b0 u0) arising from a use of `authSnaplet'
    Probable fix: add a type signature that fixes these type variable(s)
    In the first argument of `with', namely `authSnaplet'
    In the expression: with authSnaplet
    In the expression:
        with authSnaplet
      $ do { back <- access backend;
             maybeUser <- lookupByLogin back uname;
               ... }

src/Snap/Snaplet/Authentication.hs:107:16:
    Ambiguous type variable `b0' in the constraint:
      (AuthBackend b0 u) arising from a use of `lookupByLogin'
    Probable fix: add a type signature that fixes these type variable(s)
    In a stmt of a 'do' expression:
        maybeUser <- lookupByLogin back uname
    In the second argument of `($)', namely
      `do { back <- access backend;
            maybeUser <- lookupByLogin back uname;
              ... }'
    In the expression:
        with authSnaplet
      $ do { back <- access backend;
             maybeUser <- lookupByLogin back uname;
               ... }
4

1 回答 1

3

我冒昧地猜测您的问题的根源在于表达式with authSnaplet。原因如下:

∀x. x ⊢ :t with authSnaplet 
with authSnaplet
  :: AuthUser u => m b (AuthSnaplet b1 u) a -> m b v a

不要介意上下文,我填写了一些虚假实例只是为了在 GHCi 中加载内容。注意这里的类型变量——很多歧义,至少有两个我希望你打算是相同的类型。处理这个问题的最简单方法可能是创建一个带有类型签名的小型辅助函数,可以稍微缩小范围,例如:

withAuthSnaplet :: (AuthUser u)
                => Handler a (AuthSnaplet b u) (Either AuthFailure u) 
                -> Handler a s (Either AuthFailure u)
withAuthSnaplet = with authSnaplet

再说一次,请原谅废话,我现在实际上没有安装 Snap,这让事情变得很尴尬。引入此函数并使用它代替with authSnapletin loginUser,允许代码为我进行类型检查。您可能需要稍微调整一下以处理您的实际实例约束。


编辑:如果上述技术不能让您b通过某种方式确定,并假设这些类型确实像它们所写的那样通用,那么b就不可能模棱两可并且没有办法解决它。

using完全with authSnaplet消除b了实际类型,但使其具有类约束的多态性。这与表达式 likeshow . read具有相同的歧义,具有依赖于实例的行为,但无法选择一个。

为避免这种情况,您大致有三种选择:

  • 显式保留模棱两可的类型,以便b在 的实际类型中的某处找到它loginUser,而不仅仅是上下文。由于您的应用程序上下文中的其他原因,这可能是不可取的。

  • 通过仅适用with authSnaplet于适当的单态值来消除多态性。如果类型是事先知道的,就没有歧义的余地。这可能意味着放弃处理程序中的一些多态性,但是通过将事物分开,您可以将单态性限制为仅关心什么b的代码。

  • 使类约束本身明确。如果在实践中,三个类型参数 toHasAuth在某种程度上相互依赖,使得任何sand都只有一个有效实例u,那么来自其他 to 的函数依赖b将是完全合适的。

于 2011-12-17T17:36:15.207 回答