Bundling constraints with fundeps
I've got a function foo
with a litany of constraints. Of course these constraints must appear in the signatures of functions that use foo
, so what I'm trying to do is wrap the foo
constraints in a type synonym FooCtx ab ... :: Constraint
. As an example,
foo :: (A a, B b, C c, ...) => a -> b -> c
bar :: (A a, B b, C c, ...) ...
bar = ... foo ...
would become
type FooCtx a b c ... = (A a, B b, C c, ...)
foo :: (FooCtx a b c) => a -> b -> c
bar :: (FooCtx a b c) => ...
This works great if all of the types are exposed. However, I'm using functional dependencies to generate some of the types in the constraint list, and those types don't appear in the signature of foo
. For example:
class Bar a b | a -> b
foo (Bar a b, ...) => a -> a
GHC won't accept type FooCtx a = (Bar ab)
because b
isn't bound on the LHS. Neither can I use type FooCtx ab = (Bar ab)
because b
isn't in scope in the signature of foo
. The signature of foo
would be foo :: (FooCtx a ?) => a -> a
.
One unsatisfactory solution is to put one of the constraints in the foo
signature with FooCtx
to bring the fundep type in to scope:
class Bar a b | a -> b
type FooCtx a b = ...
foo (Bar a b, FooCtx a b) => a -> a
but this defeats the purpose of grouping the constraints:
Until encountering this case, I assumed that Constraint synonyms could be blindly substituted for arbitrary constraint lists. The only other way I know to encapsulate constraints like this is with a class, but it suffers from the same problem: class (A a, B b, C c, ...) => FooCtx abc
can't have any hidden types on the LHS. Is there some other way I might be able to fully gather all of these constraints?
You are misunderstanding how type variables are bound. They are not bound by the tau type (the a -> a
in your example), but an implicit binder based on the full phi type ( (Bar ab) => a -> a
). This binding can be made explicit with the ExplicitForAll
GHC language extension.
In your example, when you write something like
foo :: (Bar a b) => a -> a
then the full sigma type, with explicit tyvar-binding spelled out, is the following (since in the implicit case, all tyvars from the phi type are bound here)
foo :: forall a b. (Bar a b) => a -> a
This means there is no problem in using a constraint alias the same way: if you have eg
type FooCtx a b = (Bar a b, Num a, Eq a)
then the following is a valid type signature:
foo' :: forall a b. (FooCtx a b) => a -> a
and thus, the following shorthand is valid as well:
foo' :: (FooCtx a b) => a -> a
This problem can be solved with TypeFamilies
and FlexibleContexts
.
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}
We have three classes, A
, B
, and C
and your original foo
function.
class A a
class B a
class C a
foo :: (A a, B b, C c) => a -> b -> c
foo = undefined
The Bar
class uses a type family to figure out what B
goes with an a
. I've added an additional function to it just to write the example foo'
.
class Bar a where
type BofA a :: *
aToB :: a -> BofA a
foo'
is a function that doesn't have any inputs or outputs that are B
, but still uses foo
in its implementation. It requires that the BofA
type associated with a
satisfy the B
constraint. This signature requires flexible contexts.
foo' :: (A a, Bar a, B (BofA a), C c) => a -> a -> c
foo' x y = foo x (aToB y)
链接地址: http://www.djcxy.com/p/43078.html
上一篇: 将字典变成约束
下一篇: 用fundeps捆绑约束