依赖类约束的不明确类型变量

时间:2011-12-17 16:49:54

标签: haskell functional-programming typeclass haskell-snap-framework lenses

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

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

如何更改以下代码,使back的类型为b,而不使用例如ScopedTypeVariables 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;
               ... }

完整错误:

{{1}}

1 个答案:

答案 0 :(得分: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 authSnaplet中的loginUser,允许代码为我键入check。您可能需要稍微调整一下来处理实际的实例约束。


编辑:如果上述技术不允许您通过某种方式确定b,并假设这些类型确实是为了通用而非写入,然后b是不明确的模棱两可的,没有办法解决它。

使用with authSnaplet完全从实际类型中删除b,但在其上留下多态​​,并带有类约束。这与show . read这样的表达式具有相同的歧义,具有依赖于实例的行为,但无法选择一个。

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

  • 明确保留不明确的类型,以便在b的实际类型中找到loginUser,而不仅仅是上下文。在您的申请中,出于其他原因,这可能是不受欢迎的。

  • 通过仅将with authSnaplet应用于适当的单态值来删除多态性。如果事先知道类型,就没有模棱两可的余地。这可能意味着放弃处理程序中的某些多态性,但是通过将事物分开,可以将单态限制为仅关注b所关注的代码。

  • 使类约束本身明确无误。如果HasAuth的三个类型参数在某种程度上是相互依赖的,那么任何su只有一个有效的实例,那么其他参数的功能依赖性到b是完全合适的。