我正在尝试为归纳数据类型创建一个灵活的表示(它描述了具有数据类型和模式匹配的 lambda 演算的一个版本)。这里的灵活性应该意味着可以很容易地在节点上添加额外的数据(自由 comonad 风格)或进行替换(自由 monad 风格)。所以这就是我所拥有的:
type Tie f φ = φ (f φ)
type Id = String
type Var = Id
type Con = Id
data Pat φ = PVar Var
| PCon Con [Tie Pat φ]
| PWildcard
data Expr φ = EVar Var
| ECon Con
| EApp (Tie Expr φ)
| ELam (Tie Pat φ) (Tie Expr φ)
当我想派生
Show
实例时,麻烦就来了。当然,我可以这样做:{-# LANGUAGE FlexibleContexts, UndecidableInstances #-}
{-# LANGUAGE StandaloneDeriving #-}
deriving instance (Show (φ (Pat φ))) => Show (Pat φ)
deriving instance (Show (φ (Expr φ)), Show (φ (Pat φ))) => Show (Expr φ)
但是当归纳结构变得更加复杂时,手写上下文变得笨拙。
理想情况下,我希望能够写出类似的东西
{-# LANGUAGE RankNTypes #-}
deriving instance (forall a. Show (φ a)) => Show (Expr φ)
表示仿函数 φ 在某种意义上应该对 Show 实例“透明”。
有没有办法做这样的事情?
最佳答案
一个可能的解决方案将涉及
class Show1 f where
showsPrec1 :: Show a => Int -> f a -> ShowS
这个类型类在
prelude-extras
中也有类似的定义。不幸的是,Haskell 的派生机制不会利用它,因此无法使用。一个可能的替代方法可能是使用新的 GHC.Generics
和 DefaultSignatures
来创建,尽管不是“派生”的,但至少是微不足道的实例。instance (Show1 φ) => Show (Pat φ)
instance (Show1 φ) => Show (Expr φ)
现在是困难的部分:实际使用
GHC.Generics
。可以使用的是instance (Show1 f, Show a) => Show (f a) where showsPrec = showsPrec1
但是,这具有需要
OverlappingInstances
的极端缺点(除了其他问题)。一种可能的解决方案是定义一个 shadows Show
类。class Show' a where showsPrec' ...
instance (Show1 f, Show' a) => Show' (f a) where ...
如果所有
GHC.Generics
机制都到位( GShow
、 GShow1
、默认签名等),那么最终结果看起来不会太糟糕。instance (Show1 φ) => Show' (Pat φ)
instance (Show1 φ) => Show (Pat φ) where showsPrec = showsPrec'
instance (Show1 φ) => Show' (Expr φ)
instance (Show1 φ) => Show (Expr φ) where showsPrec = showsPrec'
然而,假设所需的工作量并没有那么糟糕(它非常糟糕),必须手动设置某些类型以从
showsPrec'
转发到 showsPrec
( Char
、 Bool
等),并且所有使用的类型都将具有要成为 Show'
的实例,虽然如果 Generic
或 Generic1
的实例微不足道,但肯定不方便。