我有一个带有一连串限制的函数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
但这违背了对约束进行分组的目的:
在遇到这种情况之前,我假设Constraint同义词可以盲目地替换任意约束列表。我知道封装这样的约束的另一种方法是使用类,但它遇到同样的问题:class (A a, B b, C c, ...) => FooCtx a b c
在LHS上不能有任何隐藏类型。还有其他方法可以完全收集所有这些约束吗?
答案 0 :(得分:5)
您误解了类型变量的绑定方式。它们是不受tau类型(示例中为a -> a
)的约束,但是基于完整的phi类型((Bar a b) => a -> a
)的隐式绑定器。可以使用ExplicitForAll
GHC语言扩展来明确此绑定。
在您的示例中,当您编写类似
的内容时foo :: (Bar a b) => a -> a
然后完整的sigma类型,明确的tyvar绑定拼写出来,如下(因为在隐式的情况下,来自phi类型的所有tyvars都绑定在这里)
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
因此,以下简写也是有效的:
foo' :: (FooCtx a b) => a -> a
答案 1 :(得分:1)
TypeFamilies
和FlexibleContexts
可以解决此问题。
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
我们有三个类,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
类使用类型系列来确定B
与a
的关系。我添加了一个额外的函数,只是为了编写示例foo'
。
class Bar a where
type BofA a :: *
aToB :: a -> BofA a
foo'
是一个函数,它没有B
的任何输入或输出,但在其实现中仍然使用foo
。它要求与BofA
关联的a
类型满足B
约束。此签名需要灵活的上下文。
foo' :: (A a, Bar a, B (BofA a), C c) => a -> a -> c
foo' x y = foo x (aToB y)