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