4

我正在为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; 
       ... } 
+0

Haskell中的主要下划线通常表示“无关”值。据我所知这是纯粹的风格(编译器只是没有警告未使用的值),但我不会使用它们作为访问函数。 – 2011-12-17 16:57:04

+0

AuthBackend是一个多参数类型的类。这可能是你需要功能依赖性,比如说“class [AuthBackend b u | u - > b]”。这意味着对于任何类型的“u”,只能有一个对应的类型“b”,它是这个类的一个实例。 – 2011-12-17 17:02:17

+1

@Paul Johnson:主要的下划线是你如何使用'data-lens-template'派生镜片。他们从不直接使用。另外,我认为放弃警告只适用于像这样命名的参数。 – ehird 2011-12-17 17:12:29

回答

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 

再次,原谅废话,我实际上没有安装在快那一刻,这让事情变得尴尬。介绍此功能并使用它代替with authSnapletloginUser,允许代码为我输入检查。您可能需要稍微调整一下以处理实际的实例约束。


编辑:如果上面的方法不会让你通过某种手段明确b,并假设类型的真正意图是为他们写为通用,然后b是不可能暧昧并没有办法绕过它。

使用with authSnaplet完全从实际类型中消除了b,但是使其具有多态性并具有类限制。这与像show . read这样的表达式具有相同的含糊性,具有依赖于实例的行为,但没有办法选择它。

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

  • 保留暧昧类型明确,使b在实际类型的loginUser,而不仅仅是上下文发现某处。由于其他原因,在您的应用程序中,这可能不合需要。

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

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

+0

这并没有歧义'b'。具有authSnaplet的'真实类型'是(HasAuth sbu,MonadSnaplet m)=> ma(AuthSnaplet bu)r - > masr,并且引入'withAuthSnaplet :: HasAuth sbu => Handler a(AuthSnaplet bu) AuthFailure u) - > Handler as(或者AuthFailure u)都没有改变模糊。 – dflemstr 2011-12-17 18:16:54

+0

@dflemstr:它是否消除了u的歧义,至少? – 2011-12-17 18:38:33

+0

@ c-a-mccann是的,实际上。 – dflemstr 2011-12-17 19:23:42