使用 fundeps 捆绑约束
Bundling constraints with fundeps
我有一个函数 foo
有很多限制。当然,这些约束必须出现在使用 foo
的函数的签名中,所以我要做的是将 foo
约束包装在类型同义词 FooCtx a b ... :: Constraint
中。例如,
foo :: (A a, B b, C c, ...) => a -> b -> c
bar :: (A a, B b, C c, ...) ...
bar = ... foo ...
会变成
type FooCtx a b c ... = (A a, B b, C c, ...)
foo :: (FooCtx a b c) => a -> b -> c
bar :: (FooCtx a b c) => ...
如果公开了所有类型,这将非常有效。然而,我正在使用函数依赖来生成约束列表中的一些类型,这些类型没有出现在 foo
的签名中。例如:
class Bar a b | a -> b
foo (Bar a b, ...) => a -> a
GHC 不接受 type FooCtx a = (Bar a b)
因为 b
没有绑定到 LHS。我也不能使用 type FooCtx a b = (Bar a b)
,因为 b
不在 foo
签名的范围内。 foo
的签名将是 foo :: (FooCtx a ?) => a -> a
.
一个不令人满意的解决方案是在 foo
签名中放置一个约束 FooCtx
以将 fundep 类型纳入范围:
class Bar a b | a -> b
type FooCtx a b = ...
foo (Bar a b, FooCtx a b) => a -> a
但这违背了对约束进行分组的目的:
在遇到这种情况之前,我假设约束同义词可以盲目地替换任意约束列表。我知道的唯一另一种封装约束的方法是使用 class,但它遇到同样的问题:class (A a, B b, C c, ...) => FooCtx a b c
在 LHS 上不能有任何隐藏类型。有没有其他方法我可以完全收集所有这些限制?
您误解了类型变量的绑定方式。它们 不是 受 tau 类型(您的示例中的 a -> a
)约束,而是基于完整 phi 类型((Bar a b) => a -> a
)的隐式绑定器。这种绑定可以通过 ExplicitForAll
GHC 语言扩展来明确。
在您的示例中,当您编写类似
的内容时
foo :: (Bar a b) => a -> a
那么完整的 sigma 类型,带有明确的 tyvar 绑定,如下所示(因为在隐式情况下,phi 类型的所有 tyvar 都在这里绑定)
foo :: forall a b. (Bar a b) => a -> a
这意味着以相同的方式使用约束别名没有问题:如果您有例如
type FooCtx a b = (Bar a b, Num a, Eq a)
那么下面是一个有效的类型签名:
foo' :: forall a b. (FooCtx a b) => a -> a
因此,以下 shorthand 也是有效的:
foo' :: (FooCtx a b) => a -> a
这个问题可以用 TypeFamilies
和 FlexibleContexts
来解决。
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
我们有三个 classes,A
、B
和 C
以及您原来的 foo
函数。
class A a
class B a
class C a
foo :: (A a, B b, C c) => a -> b -> c
foo = undefined
Bar
class 使用类型族来确定 B
与 a
的关系。我已经添加了一个额外的功能来编写示例 foo'
.
class Bar a where
type BofA a :: *
aToB :: a -> BofA a
foo'
是一个函数,它没有任何 B
的输入或输出,但在其实现中仍然使用 foo
。它要求与 a
关联的 BofA
类型满足 B
约束。此签名需要灵活的上下文。
foo' :: (A a, Bar a, B (BofA a), C c) => a -> a -> c
foo' x y = foo x (aToB y)
我有一个函数 foo
有很多限制。当然,这些约束必须出现在使用 foo
的函数的签名中,所以我要做的是将 foo
约束包装在类型同义词 FooCtx a b ... :: Constraint
中。例如,
foo :: (A a, B b, C c, ...) => a -> b -> c
bar :: (A a, B b, C c, ...) ...
bar = ... foo ...
会变成
type FooCtx a b c ... = (A a, B b, C c, ...)
foo :: (FooCtx a b c) => a -> b -> c
bar :: (FooCtx a b c) => ...
如果公开了所有类型,这将非常有效。然而,我正在使用函数依赖来生成约束列表中的一些类型,这些类型没有出现在 foo
的签名中。例如:
class Bar a b | a -> b
foo (Bar a b, ...) => a -> a
GHC 不接受 type FooCtx a = (Bar a b)
因为 b
没有绑定到 LHS。我也不能使用 type FooCtx a b = (Bar a b)
,因为 b
不在 foo
签名的范围内。 foo
的签名将是 foo :: (FooCtx a ?) => a -> a
.
一个不令人满意的解决方案是在 foo
签名中放置一个约束 FooCtx
以将 fundep 类型纳入范围:
class Bar a b | a -> b
type FooCtx a b = ...
foo (Bar a b, FooCtx a b) => a -> a
但这违背了对约束进行分组的目的:
在遇到这种情况之前,我假设约束同义词可以盲目地替换任意约束列表。我知道的唯一另一种封装约束的方法是使用 class,但它遇到同样的问题:class (A a, B b, C c, ...) => FooCtx a b c
在 LHS 上不能有任何隐藏类型。有没有其他方法我可以完全收集所有这些限制?
您误解了类型变量的绑定方式。它们 不是 受 tau 类型(您的示例中的 a -> a
)约束,而是基于完整 phi 类型((Bar a b) => a -> a
)的隐式绑定器。这种绑定可以通过 ExplicitForAll
GHC 语言扩展来明确。
在您的示例中,当您编写类似
的内容时foo :: (Bar a b) => a -> a
那么完整的 sigma 类型,带有明确的 tyvar 绑定,如下所示(因为在隐式情况下,phi 类型的所有 tyvar 都在这里绑定)
foo :: forall a b. (Bar a b) => a -> a
这意味着以相同的方式使用约束别名没有问题:如果您有例如
type FooCtx a b = (Bar a b, Num a, Eq a)
那么下面是一个有效的类型签名:
foo' :: forall a b. (FooCtx a b) => a -> a
因此,以下 shorthand 也是有效的:
foo' :: (FooCtx a b) => a -> a
这个问题可以用 TypeFamilies
和 FlexibleContexts
来解决。
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
我们有三个 classes,A
、B
和 C
以及您原来的 foo
函数。
class A a
class B a
class C a
foo :: (A a, B b, C c) => a -> b -> c
foo = undefined
Bar
class 使用类型族来确定 B
与 a
的关系。我已经添加了一个额外的功能来编写示例 foo'
.
class Bar a where
type BofA a :: *
aToB :: a -> BofA a
foo'
是一个函数,它没有任何 B
的输入或输出,但在其实现中仍然使用 foo
。它要求与 a
关联的 BofA
类型满足 B
约束。此签名需要灵活的上下文。
foo' :: (A a, Bar a, B (BofA a), C c) => a -> a -> c
foo' x y = foo x (aToB y)