Ambiguous type variables for dependent class constraints

I am writing a new authentication system for the Snap web framework because the built-in module is not modular enough and it has some functions that are redundant / dead for my application. However, this problem is not related to Snap.

In doing so, I ran into a problem with undefined type constraints. In the following code, it seems obvious to me that the back type can only be a variable of type b in the type of functions, but GHC complains that the type is ambiguous.

How can I change the following code so that the back type is b , without using, for example, ScopedTypeVariables (because the problem is with the restriction, and not with the too general types)? Is there any kind of functional dependency somewhere?

Corresponding class types:

 data AuthSnaplet bu = AuthSnaplet { _backend :: b , _activeUser :: Maybe u } -- data-lens-template:Data.Lens.Template.makeLens -- data-lens:Data.Lens.Common.Lens -- generates: backend :: Lens (AuthSnaplet bu) 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 bu where save :: MonadIO m => b -> u -> mu lookupByLogin :: MonadIO m => b -> Text -> m (Maybe u) destroy :: MonadIO m => b -> u -> m () -- snap:Snap.Snaplet.Snaplet class AuthBackend bu => HasAuth sbu where authSnaplet :: Lens s (Snaplet (AuthSnaplet bu)) 

Failed to execute code:

 -- snap:Snap.Snaplet.with :: Lens v (Snaplet v') -> mbv' a -> mbva -- data-lens-fd:Data.Lens.access :: MonadState am => Lens ab -> mb loginUser :: HasAuth sbu => Text -> Text -> Handler as (Either AuthFailure u) loginUser uname passwd = with authSnaplet $ do back <- access backend maybeUser <- lookupByLogin back uname -- !!! type of back is ambiguous !!! -- ... For simplicity sake, let say the function ends like this: return . Right . fromJust $ maybeUser 

Full error:

 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; ... } 
+4
source share
1 answer

I would venture to guess that the root of your problem is in the expression with authSnaplet . That's why:

 βˆ€x. x ⊒ :t with authSnaplet with authSnaplet :: AuthUser u => mb (AuthSnaplet b1 u) a -> mbva 

Ignore the context, I filled some dummy instances just for uploading files to GHCi. Notice the type variables here - a lot of ambiguity, and at least two that I expect you intend to be the same type. The easiest way to handle this is probably to create a little helper function with a type signature that narrows some things, like this:

 withAuthSnaplet :: (AuthUser u) => Handler a (AuthSnaplet bu) (Either AuthFailure u) -> Handler as (Either AuthFailure u) withAuthSnaplet = with authSnaplet 

Again, excuse the stupidity, in fact I don’t have Snap, which makes things uncomfortable. Introducing this function and using it instead of with authSnaplet in loginUser allows you to enter code for verification. You may need to adjust the situation a bit to cope with your actual instance limitations.


Edit: If the method described above does not allow you to mute b any way and assuming that the types are really intended as general than what they are written, then b impossible ambiguously and there is no way around it.

Using with authSnaplet completely eliminates b from the actual type, but leaves it polymorphic with the class constraint on it. This is the same ambiguity as an expression like show . read show . read , has instance-specific behavior, but no way to select it.

To avoid this, you have approximately three options:

  • Store the ambiguous type explicitly, so b is somewhere in the actual loginUser type, and not just in the context. This may not be desirable for other reasons in the context of your application.

  • Remove polymorphism by applying only with authSnaplet to the corresponding monomorphic values. If the types are known in advance, there is no room for ambiguity. This potentially means giving up any polymorphism in your handlers, but by sorting things out, you can restrict monomorphism to only code that takes care that b .

  • Make class restrictions unambiguous. If three parameters of type HasAuth in practice are somewhat interdependent, so that for any s and u will be only one valid instance, then the functional dependence on the rest to b will be fully consistent.

+3
source

Source: https://habr.com/ru/post/1386837/


All Articles