以下是我面临的非常类似问题的简化版。
考虑以下类型和函数f1
:
{-# LANGUAGE RankNTypes #-}
newtype D t = D t deriving Functor
newtype T t = T { getT :: t }
f1 :: (forall t'. t' -> D t') -> T t -> D (T t)
请注意,f1
实际上可以是id
,因为如果我们通过了适用于所有t
的函数,我们当然可以将它专门化为:
f1 = id
现在让我们考虑"反向"功能,f2
:
f2 :: (T t -> D (T t)) -> t -> D t
这" unspecialises"该功能可以按如下方式实现:
f2 f x = getT <$> (f (T x))
我们可以将f2
和f1
组合如下,这基本上是一个身份函数:
g :: (forall t'. t' -> D t') -> t -> D t
g x = f2 (f1 x)
确实,g
几乎等同于id
函数,实际上我们可以如下定义g
:
g = id
所以我们建立了f2 . f1 == id
。
但是当我们写f2 . f1
时,我怀疑GHC可能无法将其编译为id
,因为f2
至少做了一些非常重要的工作。
我想为f2 . f1
写一条重写规则,这是我的尝试:
{-# RULES
"f2f1" forall x. f2 (f1 x) = g x
#-}
g
可以定义为id
,我认为这可能很好。
但不幸的是,这无法编译。我怀疑这是由于f1
中排名较高的类型。
我意识到如果我改变了f1
的类型签名,如下所示:
f1 :: (t -> D t) -> T t -> D (T t)
f1 f x = T <$> f (getT x)
我可以编写如下的重写规则:
{-# RULES
"f2f1" forall x. f2 (f1 x) = x
#-}
但现在每当我使用f1
时,它都不仅仅是id
,而是相当复杂。
有没有办法编写重写规则,例如f2 . f1 == id
,而不给f1
非id
样式实现?
更多信息:
请注意,在我的实际问题中,D
和T
都不是新类型。
D
是Functor f
,T
实际上是Coyoneda
,来自this previous question regarding newtype deriving。
答案 0 :(得分:4)
RULES
中的多态自由变量必须具有类型签名。只需使用
{-# RULES
"f2/f1" forall (x :: forall t. t -> D t). f2 (f1 x) = x
#-}
答案 1 :(得分:2)
g :: (forall t'. t' -> D t') -> t -> D t
g x = f2 (f1 x)
并使用-O2 -ddump-simpl -dsuppress-coercions -dsuppress-idinfo
进行编译,我得到了
Clinton.g1
:: forall t_aGp.
(forall t'_aqH. t'_aqH -> D t'_aqH) -> t_aGp -> D (T t_aGp)
Clinton.g1 =
\ (@ t_aGp)
(x_aqY :: forall t'_aqH. t'_aqH -> D t'_aqH)
(x1_Xrz :: t_aGp) ->
x_aqY @ (T t_aGp) (x1_Xrz `cast` ...)
-- RHS size: {terms: 1, types: 0, coercions: 16}
g :: forall t_aqG.
(forall t'_aqH. t'_aqH -> D t'_aqH) -> t_aqG -> D t_aqG
g = Clinton.g1 `cast` ...
忽略在代码生成中消失的类型参数和强制转换,这基本上就是
g f y = f y
非常好。根据您的重写规则,我们得到
g :: forall t_aqG.
(forall t'_aqH. t'_aqH -> D t'_aqH) -> t_aqG -> D t_aqG
g =
\ (@ t_aGr) (x_aqY :: forall t'_aqH. t'_aqH -> D t'_aqH) ->
x_aqY @ t_aGr
基本上是
g f = f
更好?好吧,有点。他们实际上有点不同。没有重写规则,
g undefined `seq` () = ()
使用规则,
g undefined `seq` () = undefined
就我个人而言,我不想使用改变语法的重写规则,尤其是那些可以降低定义性的重写规则,所以我永远不会写那个。< / p>