由于previous question,我知道如何为特定的Mu
递归类型(如Mu NatF
或Mu (ListF a)
)编写专门的实例。现在,我想编写一个通用实例,该实例适用于应用于Mu
的所有函子。
基本上我想做的是填补以下Haskell摘录中名为<???>
的空白:
newtype Mu f = Mu (forall a. (f a -> a) -> a)
instance (Functor f, Show1 f) => Show (Mu f) where
showsPrec d (Mu f) = <???>
这怎么实现?
编辑
想象一下我想使用Mu
定义自然数。像这样:
data NatF r = Zero | Succ r
instance Functor NatF where
fmap f Zero = Zero
fmap f (Succ r) = Succ (f r)
type NatMu = Mu NatF
现在,我想显示类型为NatMu
的值,所以我这样做:
instance Show (Mu NatF) where
show (Mu f) = f alg where
alg Zero = "Zero"
alg (Succ n) = "(Succ " ++ n ++ ")"
一切正常,每个人都很高兴,但是...如果我想定义data NatNu = Nu NatF
或data ListMu a = Mu (ListF a)
怎么办?我将需要为每个变体创建一个几乎相同的实例。为什么不对其进行抽象处理,以使我的定义仅随着函子和递归方案的数量线性增长,而不是平方增长?
我不知道该怎么做。我只是从大量的汇编+ C背景(嵌入式编程和数字代码)中学习Haskell的。我的大脑捷径包括这些抽象,类型,提升...,我试图从头开始理解它实现了大多数基本概念,但是我可以解决这个特殊问题。
EDIT2
我设法写了一些有用的东西,但对我来说看起来很奇怪。而且,它需要一些丑陋的扩展名,例如UndecidableInstances
。
instance Show (NatF String) where
show Zero = "Zero"
show (Succ n) = "(Succ " ++ n ++ ")"
instance (Functor f, Show (f String)) => Show (Mu f) where
show (Mu f) = f $ show
是否有更好的方法(不需要花哨的Haskell扩展)?
答案 0 :(得分:2)
首先,让我重写您的Mu NatF
实例以定义showsPrec
而不是show
,这通常是首选的方式(稍后会变得很重要)。
instance Show (Mu NatF) where
showsPrec p (Mu f) = showParen (p>9) $ f alg where
alg :: NatF ShowS -> ShowS
alg Zero = ("Zero"++)
alg (Succ n) = ("(Succ "++) . n . (")"++)
这里什么都没改变;我基本上只是将++
串联运算符替换为差异列表的组合-串联。 (之所以会优先使用这些选项,主要是因为其左关联列表级联的烦人的 O ( n 2 )复杂性;这有点历史性尴尬。)
现在,如果要切换到其他函子,则唯一需要更改的就是alg
函数。您需要通用的
alg :: Show1 f => f ShowS -> ShowS
好吧,这几乎是 the function which the Show1
class is about:
showsPrec1 :: (Show1 f, Show a) => Int -> f a -> ShowS
(这基本上与show1 :: (Show1 f, Show a) => f a -> String
等效,尽管库中没有提供。)
您只需要代替{em> showing a
参数,而只需逐字粘贴到ShowS
中即可。我会这样做:
newtype UnquotedString = UnquotedString {getUnquotedString :: ShowS}
instance Show UnquotedString where
showsPrec _ (UnquotedString s) = s
我们总是想要括号;这可以通过始终为Priority参数设置10来实现。
alg = showsPrec1 10 . fmap UnquotedString
IMO,使用Show1
的整个方法相当尴尬。这是一个令人讨厌的实例化类,与您可以简单派生的Show
有很大不同。因此,实际上,我发现使用(Functor f, Show (f String))
更整洁的方法。不必太在意-XUndecidableInstances
–这是必需的,因为您以某种方式“重新调用”该类,这样编译器在进行类型检查时不会最终陷入循环,但这并不是一个大问题
尽管如此,您仍然需要避免使用多余的引号。