我正在为Snap web框架编写一个新的身份验证系统,因为内置的身份验证系统不够模块化,而且它的一些功能对我的应用程序来说是冗余的/"累赘"的。不过,这个问题与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;
... }
我冒昧地猜测您的问题的根源在于表达式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,这让事情变得很尴尬。引入这个函数,并用它代替loginUser
中的with authSnaplet
,允许代码为我进行类型检查。你可能需要稍微调整一下来处理实际的实例约束。
Edit:如果上述技术不能让您通过某种方式确定b
,并且假设类型确实打算像它们编写的那样通用,那么b
是不可能含糊的,并且没有办法绕过它。
使用with authSnaplet
将b
完全从实际类型中消除,但使其具有类约束的多态。这与像show . read
这样的表达式具有相同的模糊性,具有依赖实例的行为,但无法选择一个。
显式地保留模棱两可的类型,这样
b
就可以在loginUser
的实际类型中找到,而不仅仅是在上下文中。在应用程序的上下文中,由于其他原因,这可能是不可取的。删除多态性,只对适当的单态值应用
with authSnaplet
。如果提前知道类型,就没有歧义的余地。这可能意味着在处理程序中放弃一些多态性,但是通过将事情分开,您可以将单态限制为只关心b
是什么的代码。使类约束本身明确。如果
HasAuth
的三个类型参数在实践中在某种程度上相互依赖,以至于任何s
和u
都只有一个有效实例,那么从其他类型参数到b
的功能依赖将是完全合适的。