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 обычно обозначают значение «не заботятся». Насколько я знаю, это чисто стилистический (компилятор просто не предупреждает о неиспользуемых значениях), но я бы не использовал их как функции доступа. –

+0

AuthBackend - это тип многопараметрического типа. Возможно, вам нужна функциональная зависимость, например, «class [...] AuthBackend b u | u -> b». Это означает, что для любого типа «u» может быть только один соответствующий тип «b», который является экземпляром этого класса. –

+1

@Paul Johnson: Ведущие подчеркивания - это то, как вы получаете линзы с использованием «шаблона данных-объективов». Они никогда не используются напрямую. Кроме того, я думаю, что отказ от предупреждения относится только к таким параметрам. – ehird

ответ

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 authSnaplet в loginUser, позволяет ввести код для проверки. Возможно, вам придется немного изменить настройки, чтобы справляться с вашими фактическими ограничениями экземпляра.


Edit: Если выше метод не позволяет закреплять b некоторыми средствами, и при условии, что типы действительно предназначены быть столь же общим, как они написаны, то b является невероятно неоднозначным и нет никакого способа обойти это.

Использование with authSnaplet полностью исключает b из фактического типа, но оставляет его полиморфным с ограничением класса на нем. Это та же двусмысленность, что выражение, подобное show . read, имеет, например, поведение, зависящее от экземпляра, но не способ выбрать его.

Чтобы избежать этого, у вас есть примерно три варианта:

  • Сохранил неоднозначный тип явно, так что b находится где-то в самом типе loginUser, не только контекст. Это может быть нежелательным по другим причинам в контексте вашей заявки.

  • Удалите полиморфизм, применяя только with authSnaplet к соответствующим мономорфным значениям. Если типы известны заранее, нет места для двусмысленности. Это потенциально означает отказ от какого-либо полиморфизма в ваших обработчиках, но, разбирая вещи, вы можете ограничить мономорфизм только кодом, который заботится о том, что такое b.

  • Сделать ограничения класса недвусмысленными. Если три параметра типа до HasAuth на практике являются взаимозависимыми до некоторой степени такими, что только один действительный экземпляр для любых s и u, то функциональная зависимость от остальных до b будет полностью уместной.

+0

Это не устранило 'b'. «Реальный тип» 'с authSnaplet' является' (HasAuth sbu, MonadSnaplet m) => ma (AuthSnaplet bu) r -> masr' и представляет 'withAuthSnaplet :: HasAuth sbu => Обработчик a (AuthSnaplet bu) (Либо AuthFailure u) -> Обработчик как (либо AuthFailure u) 'не изменил неоднозначность. – dflemstr

+0

@dflemstr: Удалось ли устранить двусмысленность на 'u', по крайней мере? –

+0

@ c-a-mccann Да, на самом деле. – dflemstr

Смежные вопросы