6

我有一个与Show相同的类,我想为每个元组类型创建一个此类的实例。通常这是通过为每个元组类型单独编写实例来完成的

instance  (Show a, Show b) => Show (a,b)  where
  showsPrec _ (a,b) s = show_tuple [shows a, shows b] s

instance (Show a, Show b, Show c) => Show (a, b, c) where
  showsPrec _ (a,b,c) s = show_tuple [shows a, shows b, shows c] s

instance (Show a, Show b, Show c, Show d) => Show (a, b, c, d) where
  showsPrec _ (a,b,c,d) s = show_tuple [shows a, shows b, shows c, shows d] s
...

为每个元组类型编写一个实例会导致大量样板文件,并且很容易看到所有showPrec实现之间共享的通用模式。为了避免这种样板,我想我可以使用Scrap你的样板中的Data.Generics并实现折叠元组,比如

showTuple = intercalate " " . gmapQ ("" `mkQ` show)

showTuple由于某种原因不起作用

> showTuple (1,2)
" "

我认为问题在于它show是多态的,因为如果我专攻showTuple它,它就会起作用

showTupleInt = intercalate " " . gmapQ ("" `mkQ` (show :: Int -> String))
> showTupleInt (1::Int,2::Int)
"1 2"

我检查了gshow的代码,它的功能与我需要的类似,但我不知道它是如何工作的。如果我尝试将其代码导入 GHCI,则会收到错误消息:

> let gshows = (\t -> showChar '('
                      . (showString . showConstr . toConstr $ t)
                      . (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t)
                      . showChar ')'
                      ) `extQ` (shows :: String -> ShowS)
<interactive>:262:59:
Could not deduce (a ~ d)
from the context (Data a)
  bound by the inferred type of
           gshows :: Data a => a -> String -> String
  at <interactive>:(259,5)-(264,44)
or from (Data d)
  bound by a type expected by the context:
             Data d => d -> String -> String
  at <interactive>:262:33-65
  `a' is a rigid type variable bound by
      the inferred type of gshows :: Data a => a -> String -> String
      at <interactive>:259:5
  `d' is a rigid type variable bound by
      a type expected by the context: Data d => d -> String -> String
      at <interactive>:262:33
Expected type: d -> String -> String
  Actual type: a -> String -> String
In the second argument of `(.)', namely `gshows'
In the first argument of `gmapQ', namely
  `((showChar ' ' .) . gshows)'
In the second argument of `(.)', namely
  `gmapQ ((showChar ' ' .) . gshows)'

所以我有两个问题:

  1. 有什么问题,showTuple我该如何修复它,使其适用于任何大小的元组
  2. 它是如何gshow工作的,为什么如果我在 GHCI 上导入它的代码会出现这个错误?

编辑:我正在学习Data.Generics,总的来说是 SYM,所以我想使用那个模块。只有当它仅使用该模块时,我才会接受答案。谢谢。

4

3 回答 3

6

我更熟悉 GHC Generics,而不是 SYB,所以我提供了一个基于 Generics 的解决方案。虽然它不能直接回答您的问题,但我希望它也有用。

{-# LANGUAGE TypeOperators, FlexibleContexts, DefaultSignatures #-}
import Data.Sequence
import GHC.Generics

class Strs' f where
    strings' :: f a -> Seq String

instance Strs' U1 where
    strings' U1 = empty

instance Show c => Strs' (K1 i c) where
    strings' (K1 a) = singleton $ show a

instance (Strs' a) => Strs' (M1 i c a) where
    strings' (M1 a) = strings' a

instance (Strs' f, Strs' g) => Strs' (f :*: g) where
    strings' (a :*: b) = strings' a >< strings' b

class Strs a where
    strings :: a -> Seq String
    default strings :: (Generic a, Strs' (Rep a)) => a -> Seq String
    strings = strings' . from

-- Since tuples have Generic instances, they're automatically derived using
-- the above default.
instance Strs () where
instance (Show a, Show b) => Strs (a, b) where
instance (Show a, Show b, Show c) => Strs (a, b, c) where
于 2013-12-27T23:12:07.183 回答
5

你是对的,showTuple因为show. 问题是mkQ想要挑选一种特定类型:

mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r

类型签名中的b每次使用都必须是一种特定类型mkQ- 如果没有类型签名,默认规则可能会选择一些东西(不确定是什么!),而使用类型签名它会选择Int.

showTupleInt确实适用于任何大小的元组,但当然不适用于任何类型的元组。

在 GHCi 中定义的问题gshows在于,它确实需要一个类型签名才能进行类型检查,因为gshows在其自己的定义中递归使用与原始调用不同的类型。如果没有类型签名,类型检查器希望 的定义具有gshows与使用完全相同的类型变量实例化gshows- 这显示为Could not deduce (a ~ d)类型错误。

您可以通过将其放入带有和不带有类型签名的源文件中来查看这一点 - 如果您第一次使用:set -XNoMonomorphismRestriction.

gshows由于以下类型而完全有效gmapQ

gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u]

与之相反mkQ,它采用的参数本身就是多态的——注意嵌套的forall

尽管您showTuple也使用gmapQ,但为时已晚 -mkQ已经通过强制show仅在一种类型上工作而引起了麻烦。

您也不能show直接使用 with gmapQ,因为约束是不同的 -gmapQ想要可以在 的任何实例上工作的东西Data,而showShow. gshows从未真正Show通用地使用类型类,尽管它确实使用shows了专门的 to String

在这样的情况下很难证明是否定的,但我相当确定你不能写任何类似的东西showTuple会使用Show多态地使用 just syb,因为它根本没有任何可以“识别”的类型有一个特定的例子。这就是syb-with-class存在的原因。

此外,如果您确实想要仅在类型结构的单个级别上工作的东西,即显示任何大小的元组但对元组的元素使用其他东西,那么syb可以说是错误的解决方案,因为它是为递归操作而设计的并且在数据结构的任何级别上查找事物。我的观点是,该GHC.Generics解决方案是实现showTuple.

于 2013-12-27T20:59:49.470 回答
5

您可以使用syb-with-class。它早于-XConstraintKinds,因此您需要编写一个Sat 类的实例并派生该库派生的 Data 类。这是一个示例,它与 showTuple 示例非常接近,除了我添加了一些 {}:

{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, TemplateHaskell, UndecidableInstances #-}
import Data.Generics.SYB.WithClass.Basics
import Data.Generics.SYB.WithClass.Instances
import Data.Generics.SYB.WithClass.Derive

data A a b c = A a b c deriving Show
data B a = B a deriving Show
data C a = C a deriving Show

derive [''A,''B,''C]

data ShowD a = ShowD { showD :: a -> String -> String }
instance (Show a) => Sat (ShowD a) where
    dict = ShowD shows

gshow x = case gfoldl ctx 
                (\ (s, f) x -> (s . ("{"++) . showD dict x . ("}"++) , f x))
                (\y -> (id ,y))
                x
        of (str,_) -> str ""
    where
        ctx :: Proxy ShowD
        ctx = undefined

x1 = A (B 'b') (C "abc") (B ())

{-
>>> gshow x1
"{B 'b'}{C \"abc\"}{B ()}"

>>> show x1
"A (B 'b') (C \"abc\") (B ())"
-}

get 的第二个参数gfoldl调用shows (B 'b')shows (C "abc")shows (B ())感谢showD dictwhich 获取shows具有正确类型的函数。

于 2013-12-28T03:08:05.207 回答