Неоднозначные переменные типа для зависимых ограничений класса - PullRequest
4 голосов
/ 17 декабря 2011

Я пишу новую систему аутентификации для 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 Ответ

3 голосов
/ 17 декабря 2011

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


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

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

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

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

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

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

...