Haskell – Unclear Type Variables of Dependent Constraints

I am writing a new authentication system for the Snap web framework, because the built-in authentication system is not modular enough, and it has some redundancy/”self-respect” features. However, this The problem has nothing to do with Snap.

In doing so, I encountered the problem of fuzzy type constraints. In the code below, it is obvious to me that the returned type can only be a function type In the type variable b, but GHC complains that the type is not clear.

How to change the following code so that the type on the back is b, without using, for example, ScopedTypeVariables (because the problem lies in the constraints, rather than having too general types )? Is there a need for functional dependency somewhere?

Related 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< br /> 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 code:

-- snap:S nap.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< br /> 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

Complete 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 <- ac cess 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;
... }

I took the liberty to guess the source of the problem It lies in the expression of authSnaplet. The reason is as follows:

∀x. x ⊢ :t with authSnaplet 
with authSnaplet
:: AuthUser u => mb (AuthSnaplet b1 u) a -> mbva

Don’t mind the context, I filled in some fake examples just to load things in GHCi. Note the type variables here-a lot of ambiguity, at least two I hope you intend to be the same type. The easiest way to deal with this may be to create a small helper function whose type signature can narrow the scope, for example:

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

Again, forgive the nonsense, I didn’t actually install Snap, which makes things awkward. Introduce this function and use it instead of authSnaplet in loginUser, allowing the code to type check for me. You may need to adjust a bit to handle the actual Instance constraints.

Edit: If the above technology does not allow you to determine b in some way, and assume that these types are indeed intended to be as general as written, then b is impossible to be ambiguous, and there is no way Solve it.

Use with authSnaplet to completely remove b from the actual type, but there is class-constrained polymorphism on it. This is as ambiguous as an expression such as show. Read has an instance-dependent behavior , But you cannot choose one.

To avoid this, you have about three options:

> Explicitly keep the ambiguous type so that it can be found in the actual loginUser type b, not just the context. In your application, for other reasons, this may be undesirable.> Remove polymorphism by applying only the authSnaplet to the appropriate monomorphic value. If the type is known in advance, just There is no room for ambiguity. This might mean giving up some polymorphism in the handler, but by separating things, you can limit the monomorphism to only code that focuses on what b is.> Make the class constraint itself unmistakable. If HasAuth’s The three type parameters are interdependent to some extent, so there is only one valid instance for any s and u, then the functional dependency from other parameters to b will be completely appropriate.

< /p>

I am writing a new authentication for the Snap web framework Authentication system, because the built-in authentication system is not modular enough, and it has some redundancy/”self-respect” functions. However, this problem has nothing to do with Snap.

In doing so, I encountered Here comes the problem of fuzzy type constraints. In the code below, it is obvious to me that the returned type can only be the type variable b in the function type, but GHC complains that the type is not clear.

How to change The following code makes the type on the back side b instead of using, for example, ScopedTypeVariables (because the problem lies in constraints, rather than having too general types)? Is there a need for functional dependency somewhere?

Related 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< br /> 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 code:

-- snap:Snap.Sn aplet.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's sake, let's say the function ends like this:
return. Right. fromJust $maybeUser

Complete 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 back end;
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;
... }

I took the liberty to guess that the root of the problem lies in the expression using authSnaplet. The reason is as follows:

∀x. x ⊢ :t with authSnaplet 
with authSnaplet
:: AuthUser u => mb (AuthSnaplet b1 u) a -> mbva< /pre>

Don't mind the context, I filled in These fake instances are just to load things in GHCi. Note the type variables here-a lot of ambiguity, at least two I hope you intend to be of the same type. The easiest way to deal with this might be to create a small helper function whose type Signature can narrow the scope, for example:

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

Again, forgive the nonsense, I did not actually install Snap, which makes things awkward. Introduce this function, and use it Instead of the authSnaplet in loginUser, allow the code to type check for me. You may need to adjust a bit to deal with the actual instance constraints.

Edit: If the above technique does not allow you to determine b in some way, and Assuming that these types are indeed intended to be as general as they are written, then b is impossible to be ambiguous, and there is no way to solve it.

Use with authSnaplet to completely delete b from the actual type, but on it Polymorphism with class constraints. This is as ambiguous as an expression such as show. Read has an instance-dependent behavior, but you cannot choose one.

To avoid this, you have about three choices:< /p>

> Explicitly reserve the ambiguous type so that b is found in the actual loginUser type, not just the context. In your application, for other reasons, this may be undesirable.> Remove polymorphism by only applying authSnaplet to the appropriate monomorphic value. If the type is known in advance, there is no room for ambiguity. This may mean giving up some polymorphism in the handler, but by separating things, you can Monomorphism is restricted to code that only focuses on what b is.> Make the class constraint itself unambiguous. If the three type parameters of HasAuth are mutually dependent to some extent, then there is only one valid instance for any s and u, then The functional dependencies from other parameters to b will be completely appropriate.

Leave a Comment

Your email address will not be published.