基于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 ())"
-}