正如 WillNess 所展示的,您可能想要一个 newtype 来包装您的 List:
newtype Mu f = Mu {reduce :: forall a. (f a -> a) -> a}
-- I've added a field name for convenience.
data ListF a r = Nil | Cons a r
deriving (Show, Functor, Foldable, Traversable)
-- You'll probably want these other instances at some point.
newtype List a = List {unList :: Mu (ListF a)}
WillNess 还写了一个有用的fromList 函数;这是另一个版本:
fromList :: Foldable f => f a -> List a
fromList xs =
List $ Mu $ foldr (\a as g -> g (Cons a (as g))) ($ Nil) xs
现在让我们编写一个基本(不太正确)的版本。我将打开 ScopedTypeVariables 以添加类型签名,而不会令人讨厌的重复。
instance Show a => Show (List a) where
showsPrec _ xs = reduce (unList xs) go
where
go :: ListF a ShowS -> ShowS
go Nil = id
go (Cons x r) = (',':) . showsPrec 0 x . r
这将显示一个列表,类似于:
show (fromList []) = ""
show (fromList [1]) = ",1"
show (fromList [1,2]) = ",1,2"
嗯。我们需要安装前面的[ 和后面的],并以某种方式处理多余的前导逗号。这样做的一个好方法是跟踪我们是否在第一个列表元素上:
instance Show a => Show (List a) where
showsPrec _ (List xs) = ('[':) . reduce xs go False . (']':)
where
go :: ListF a (Bool -> [Char] -> [Char]) -> Bool -> [Char] -> [Char]
go Nil _ = id
go (Cons x r) started =
(if started then (',':) else id)
. showsPrec 0 x
. r True
现在我们实际上正确地展示了东西!
但实际上,我们遇到了不必要的麻烦。我们真正需要的只是一个Foldable 实例:
instance Foldable List where
foldr c n (List (Mu g)) = g $ \case
Nil -> n
Cons a as -> c a as
那么我们可以写
instance Show a => Show (List a) where
showsPrec p xs = showsPrec p (toList xs)