约束的类型别名与上下文不共享相同的变量绑定行为

Type Aliases for Constraints don't share the same variable binding behavior as Contexts

我一直在研究 -XConstraintKinds 来帮助缓解过于冗长的上下文,并且发现了变量绑定方面的一个有趣的不一致:

{-# LANGUAGE
    TypeFamilies
  , ConstraintKinds
  , FlexibleContexts
  #-}

-- works
foo :: ( callTree ~ SomeTypeFunc output
       , OtherTypeFunc input ~ callTree
       ) => input -> output


type FooCtx input output =
  ( callTree ~ SomeTypeFunc output
  , OtherTypeFunc input ~ callTree
  )

-- doesn't work
foo' :: FooCtx input output =>
        input -> output

除了将 callTree 置于顶级范围之外,是否有解决此问题的方法?

没有真正的不一致,只是

  1. 类型签名中的自由类型变量自动添加了forall个量词,所以第一种情况实际上等同于

    foo :: forall callTree output input. ( callTree ~ SomeTypeFunc output
           , OtherTypeFunc input ~ callTree
           ) => input -> output
    
  2. type 声明右侧的所有自由类型变量都必须是参数,不能有任何不在范围内的变量。

这不是特定于约束类型的,除了你不能将 forall 量词直接应用于约束的不幸事实,这意味着我不知道 general 使此类约束类型声明起作用的解决方法。

在您的特定示例中,( OtherTypeFunc input ~ SomeTypeFunc output ) 应该是等效的,但我假设您真的对这样的重写不起作用的更复杂的示例感兴趣。

我可以想到一种不同的解决方法,通过在约束右侧包含值,以不同的方式将参数更改为类型声明:

{-# LANGUAGE TypeFamilies, ConstraintKinds, FlexibleContexts, RankNTypes #-}

type family OtherTypeFunc a
type family SomeTypeFunc a

type FooCtx input output value =
  forall callTree. ( callTree ~ SomeTypeFunc output
  , OtherTypeFunc input ~ callTree
  ) => value

-- This now works
foo' :: FooCtx input output ( input -> output )

foo' = undefined