让 GHC 相信类型族声明没有重叠

Convince GHC that type family declarations are not overlapping

所以我想实现一个将值应用于函数的第 n 个参数的函数,我们称之为 inject:

inject :: (???) => a -> f -> ???

所以我声明了一个 class 代表所有适用的三元组 (n, a, f)。注意 n 必须大于 0 并且 a 取决于 fn

class (1 <= n) => Applicable n a f | f n -> a where
  type ApplyAt n a f :: Type
  inject :: a -> f -> ApplyAt n a f

然后我们可以对n做一些归纳:

instance Applicable 1 a (a -> b) where
  type ApplyAt 1 a (a -> b) = b
  inject a f = f a

instance (Applicable (n - 1) a c) => Applicable n (a :: Type) (b -> c) where
  type ApplyAt n a (b -> c) = b -> ApplyAt (n - 1) a c
  inject a f = \x -> inject @(n - 1) a (f x)

Applicable n a (b -> c) 暗示 Applicable (n - 1) a c 暗示 n - 1 >= 1n >= 2 所以这些实例声明不重叠。但是 GHC 继续抱怨:

Conflicting family instance declarations:
  ApplyAt 1 a (a -> b) = b
  ApplyAt n a (b -> c) = b -> ApplyAt (n - 1) a c

我应该怎么做才能让 GHC 相信这些类型家族声明没有重叠?

解决方案是使用归纳定义的自然 numbers/positive 整数来避免实例重叠。

data NPlus = One | Suc NPlus

class Applicable (n :: NPlus) a f | f n -> a where
  type ApplyAt n a f :: Type
  inject :: a -> f -> ApplyAt n a f

instance Applicable One a (a -> b) where
  type ApplyAt One a (a -> b) = b
  inject a f = f a

instance (Applicable n a c) => Applicable (Suc n) (a :: Type) (b -> c) where
  type ApplyAt (Suc n) a (b -> c) = b -> ApplyAt n a c
  inject a f = \x -> inject @n a (f x)

-- >>> inject @(Suc One) 1 (\y x -> (y + 3) * x) 2
-- 5
--