依赖类约束的不明确类型变量

发布于 2024-12-22 05:05:22 字数 2983 浏览 3 评论 0原文

我正在为 Snap Web 框架 编写一个新的身份验证系统,因为内置的不够模块化,它有一些对于我的应用程序来说是多余的/“自重”的功能。不过,这个问题与 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;
               ... }

I'm writing a new authentication system for the Snap web framework, because the built-in one isn't modular enough, and it has some features that are redundant/"dead weight" for my application. This problem isn't related to Snap at all, though.

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

How can I change the following code such that the type of back is b, without using e.g. ScopedTypeVariables (because the problem is with the constraint, not with having too general types)? Is there a functional dependency that is needed somewhere?

Relevant type classes:

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))

The code that fails:

-- 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

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;
               ... }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

单身狗的梦 2024-12-29 05:05:22

我大胆猜测您的问题的根源在于表达式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,这让事情变得尴尬。引入此函数,并使用它代替 loginUser 中的 with authSnaplet,允许代码为我进行类型检查。您可能需要稍微调整一下以处理实际实例的限制。


编辑:如果上述技术不能让您通过某种方式确定b,并假设这些类型确实是像它们所写的那样通用,那么 b 是不可能有歧义的,并且没有办法解决它。

与 authSnaplet 一起使用会从实际类型中完全消除 b,但会使其具有多态性并受到类约束。这与 show 等表达式具有相同的歧义性。 read 有,具有依赖于实例的行为,但无法选择一个。

为了避免这种情况,您大致有三种选择:

  • 显式保留不明确的类型,以便在 loginUser 的实际类型中的某个位置找到 b,而不仅仅是上下文。由于应用程序上下文中的其他原因,这可能是不可取的。

  • 通过仅将 with authSnaplet 应用于适当的单态值来删除多态性。如果提前知道类型,就没有歧义的余地。这可能意味着在处理程序中放弃一些多态性,但是通过将事物分开,您可以将单态性限制为仅关心 b 是什么的代码。

  • 使类约束本身明确。如果 HasAuth 的三个类型参数实际上在某种程度上是相互依赖的,因此任何 su 都只有一个有效实例>,那么从其他类到 b 的函数依赖将是完全合适的。

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

∀x. x ⊢ :t with authSnaplet 
with authSnaplet
  :: AuthUser u => m b (AuthSnaplet b1 u) a -> m b v a

Don't mind the context, I filled in some bogus instances just to load stuff in GHCi. Note the type variables here--lots 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 small, auxiliary function with a type signature that narrows things down a bit, e.g.:

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

Again, pardon the nonsense, I don't actually have Snap installed at the moment, which makes things awkward. Introducing this function, and using it in place of with authSnaplet in loginUser, allows the code to type check for me. You may need to tweak things a bit to handle your actual instance constraints.


Edit: If the above technique doesn't let you nail down b by some means, and assuming that the types really are intended to be as generic as they're written, then b is impossibly ambiguous and there's no way around it.

Using with authSnaplet eliminates b entirely from the actual type, but leaves it polymorphic with a class constraint on it. This is the same ambiguity that an expression like show . read has, with instance-dependent behavior but no way to pick one.

To avoid this, you have roughly three choices:

  • Retain the ambiguous type explicitly, so that b is found somewhere in the actual type of loginUser, not just the context. This may be undesirable for other reasons in the context of your application.

  • Remove the polymorphism, by only applying with authSnaplet to suitably monomorphic values. If the types are known in advance, there's no room for ambiguity. This potentially means giving up some polymorphism in your handlers, but by breaking things apart you can limit the monomorphism to only code that cares what b is.

  • Make the class constraints themselves unambiguous. If the three type parameters to HasAuth are, in practice, interdependent to some degree such that there will only be one valid instance for any s and u, then a functional dependency from the others to b would be completely appropriate.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文