【问题标题】:How to write a Show instance for Mu recursive types如何为 Mu 递归类型编写 Show 实例
【发布时间】:2018-11-29 10:57:08
【问题描述】:

我想为以下类型的列表编写Show 的实例:

newtype Mu f = Mu (forall a. (f a -> a) -> a)
data ListF a r = Nil | Cons a r deriving (Show)
type List a = Mu (ListF a)

模块Data.Functor.Foldable 定义了它,但它将它转换为Fix,这是我想避免的。

如何定义这个Show 实例?

【问题讨论】:

    标签: haskell show recursive-datastructures fixpoint-combinators


    【解决方案1】:

    首先定义你自己的代数

    showOneLayer :: Show a => ListF a String -> String
    showOneLayer ... = ...
    

    那么,

    instance Show a => Show (Mu (ListF a)) where
       show (Mu f) = f showOneLayer
    

    【讨论】:

    • @user3368561 是的,我想是的。
    • 如果函子f 提供了一个Show 实例,是否可以使它成为通用的?
    • @user3368561 我认为这取决于实例。只要你能得到上面的代数就可以了。
    【解决方案2】:

    口号,“跟随类型!”,在这里为我们工作,全职。

    从您的代码中,为了便于理解而进行了一些重命名,

    {-# LANGUAGE RankNTypes #-}
    
    data ListF a r = Nil | Cons a r deriving (Show)
    newtype List a = Mu {runMu :: forall r. (ListF a r -> r) -> r}
    

    这样我们就可以拥有

    fromList :: [a] -> List a
    fromList (x:xs) = Mu $ \g -> g   -- g :: ListF a r -> r
                                   (Cons x $                 -- just make all types fit
                                      runMu (fromList xs) g)
    fromList []     = Mu $ \g -> g Nil
    
    {-   or, equationally,
    runMu (fromList (x:xs)) g = g (Cons x $ runMu (fromList xs) g)
    runMu (fromList [])     g = g Nil 
    
         such that (thanks, @dfeuer!)
    runMu (fromList [1,2,3]) g = g (Cons 1 (g (Cons 2 (g (Cons 3 (g Nil))))))
    -}
    

    我们想要

    instance (Show a) => Show (List a) where
     -- show :: List a -> String
     show (Mu f) = "(" ++ f showListF ++ ")"            -- again, just make the types fit
    

    ... 我们必须产生一个字符串;我们只能致电f;它的论点是什么?根据其类型,

      where
      showListF :: Show a => ListF a String -> String   -- so that, f showListF :: String !
      showListF Nil        = ...
      showListF (Cons x s) = ...
    

    这里没有看到任何其他连接电线的方法。

    有了这个,print $ fromList [1..5] 打印出(1 2 3 4 5 )

    事实上,这是chianswer 的冗长版本。

    编辑: g 用于“代数”(谢谢,@chi!),fMu f)用于“折叠”。现在这种类型的含义变得更清楚了:给定一个“代数”(一个归约函数),Mu f 值将在折叠由这个“折叠函数”表示的“固有列表”时使用它。它表示具有一步缩减语义的列表的折叠,在折叠的每个步骤中使用它。

    【讨论】:

      【解决方案3】:

      正如 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)
      

      【讨论】:

      • 这很好。 g-juggling 在你的fromList 中有点令人费解,直到我最终放弃并手写了一些示例扩展([1,2,3])。这实际上为我澄清了一些事情。 :) 我什至可以稍微简化一下,就像fromList xs = List $ Mu $ \g -> foldr (g .: Cons) (g Nil) xs。这也使它更懒惰? (这个好吗?)。并且免费做Foldable 得到它的toList 是个好主意(我跳过了中间的所有内容)。 :) 回想起来似乎很明显,就像所有伟大的想法一样。
      • @WillNess,这有什么更懒惰的?另外,.: 是什么?我的fromList 直接基于你的。
      • 我认为foldr ... _|_ 会发散,但\g -> foldr ... _|_ 不会。 ---是的,在完成您的版本之后,我意识到它与我的答案相同(我没有努力扩展它,内容是否适合......)。但是在最终看到在一系列应用程序之后我们留下了这个g (Cons a (g (Cons b (g Nil)))) 的东西,我想,为什么不直接编码呢? --- 回复:.: 我认为这是常见传说的一部分。 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-20
      • 1970-01-01
      相关资源
      最近更新 更多