基于SYB和ad-hoc多态性的Haskell泛型编程

基于SYB和ad-hoc多态性的Haskell泛型编程,haskell,scrap-your-boilerplate,Haskell,Scrap Your Boilerplate,我有一个与相同的类,我想为每个元组类型创建一个此类的实例。通常,这是通过为每个元组类型分别编写实例来实现的 实例(Show a,Show b)=>Show(a,b)其中 showsPrec(a,b)s=show_tuple[显示a,显示b]s 实例(Show a,Show b,Show c)=>Show(a,b,c)其中 showsPrec(a,b,c)s=show_tuple[显示a,显示b,显示c]s 实例(Show a,Show b,Show c,Show d)=>Show(a,b,c,d

我有一个与相同的类,我想为每个元组类型创建一个此类的实例。通常,这是通过为每个元组类型分别编写实例来实现的

实例(Show a,Show b)=>Show(a,b)其中
showsPrec(a,b)s=show_tuple[显示a,显示b]s
实例(Show a,Show b,Show c)=>Show(a,b,c)其中
showsPrec(a,b,c)s=show_tuple[显示a,显示b,显示c]s
实例(Show a,Show b,Show c,Show d)=>Show(a,b,c,d)其中
showsPrec(a,b,c,d)s=show_tuple[显示a,显示b,显示c,显示d]s
...
每个元组类型编写一个实例会产生大量样板文件,并且很容易看到所有
showPrec
实现之间共享的公共模式。为了避免这种样板文件,我想我可以使用from并实现对元组的折叠,如

showTuple = intercalate " " . gmapQ ("" `mkQ` show)
但是由于某些原因,
showTuple
不起作用

> showTuple (1,2)
" "
我认为问题在于
show
是多态的,因为如果我专门化
showTuple
,它就会工作

showTupleInt = intercalate " " . gmapQ ("" `mkQ` (show :: Int -> String))
我已经检查了它的代码,它做了一些类似于我需要的事情,但我不知道它是如何工作的。如果我尝试将其代码导入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)'
>让gshows=(\t->showChar'('
(showString.showcontr.toConstr$t)
(foldr(.)id.gmapQ((showChar'').gshows)$t)
.showChar')”
)`extQ`(shows::String->shows)
:262:59:
无法推断(a~d)
从上下文(数据a)
由的推断类型绑定
gshows::Data a=>a->String->String
电话:(259,5)-(264,44)
或来自(数据d)
由上下文所需的类型绑定:
数据d=>d->字符串->字符串
电话:262:33-65
`a'是一个刚性类型变量,由
gshows::Data a=>a->String->String的推断类型
时间:259:5
`d'是一个刚性类型变量,由
上下文所需的类型:数据d=>d->String->String
电话:262:33
预期类型:d->String->String
实际类型:a->String->String
在“()”的第二个参数中,即“gshows”
在'gmapQ'的第一个参数中,即
`((showChar'').gshows)'
在“()”的第二个参数中,即
`gmapQ((showChar'').gshows)'
所以我有两个问题:

  • showTuple
    有什么问题,我如何修复它,使它可以与任何大小的元组一起工作
  • gshow
    是如何工作的?如果我在GHCI上导入其代码,为什么会出现该错误

  • 编辑:我正在研究
    数据.泛型和一般的SYM,所以我想使用这个模块。我只接受只使用该模块的答案。谢谢。

    你说得对,
    showTuple
    因为
    show
    的多态性而无法工作。问题在于
    mkQ
    想要选择一种特定类型:

    mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
    
    类型签名中的
    b
    对于
    mkQ
    的每次使用必须是一个特定的类型-如果没有类型签名,默认规则可能会选择某些内容(不确定是什么!),而对于类型签名,它选择
    Int

    您的
    showTupleInt
    可以处理任何大小的元组,但当然不能处理任何类型的元组

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

    您可以通过将其放入带有和不带类型签名的源文件中来看到这一点。如果您第一次使用
    :set-XNoMonomorphismRestriction
    ,则使用它键入的签名可以很好地检查,如果不使用它,则会得到与您得到的错误类似的错误

    gshows
    之所以有效,是因为
    gmapQ
    的类型:

    gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u]
    
    mkQ
    相反,它所采用的参数本身是多态的-注意嵌套的
    forall

    虽然您的
    showTuple
    也使用了
    gmapQ
    ,但为时已晚-
    mkQ
    已经造成了问题,因为它强制
    show
    只在一种类型上工作

    您也不能直接将
    show
    gmapQ
    一起使用,因为约束条件不同-
    gmapQ
    需要可以在
    数据的任何实例上工作的东西,而
    show
    show
    的约束
    gshows
    从未实际通用地使用
    Show
    类型类,尽管它确实使用
    shows
    专门用于
    String

    在这种情况下很难证明否定,但我相当肯定,您不能编写任何类似于
    showTuple
    的东西,只使用
    syb
    多态地使用
    Show
    类,因为它没有任何东西可以“识别”具有特定实例的类型。这就是为什么存在类为
    的syb


    此外,如果您确实希望某个对象只在类型结构的单个级别上工作,即显示任意大小的元组,但对元组的元素使用其他对象,那么
    syb
    可以说是错误的解决方案,因为它是为递归操作和在数据结构的任何级别查找对象而设计的。我的观点是,
    GHC.Generics
    解决方案是实现
    showTuple
    的最好的解决方案,我更熟悉GHC Generics,而不是SYB,因此我提供了一个基于Generics的解决方案。虽然它不能直接回答你的问题,但我希望它也能有用。
    gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u]
    
    {-# 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
    
    {-# 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 ())"
    -}