mar*_*iop 6 haskell scrap-your-boilerplate
我有一个与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
...
Run Code Online (Sandbox Code Playgroud)
每个元组类型编写一个实例会产生大量的样板,并且很容易看到所有showPrec
实现之间共享的公共模式.为了避免这种样板的,我想我可以使用Data.Generics从废料您的样板和实施更折过的元组,像
showTuple = intercalate " " . gmapQ ("" `mkQ` show)
Run Code Online (Sandbox Code Playgroud)
但showTuple
由于某种原因不起作用
> showTuple (1,2)
" "
Run Code Online (Sandbox Code Playgroud)
我认为问题是show
多态的事实,因为如果我专注,showTuple
那么它的工作原理
showTupleInt = intercalate " " . gmapQ ("" `mkQ` (show :: Int -> String))
Run Code Online (Sandbox Code Playgroud)
> showTupleInt (1::Int,2::Int)
"1 2"
Run Code Online (Sandbox Code Playgroud)
我已经检查了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)'
Run Code Online (Sandbox Code Playgroud)
所以我有两个问题:
showTuple
如何解决它,以便它可以使用任何大小的元组gshow
如果我在GHCI上导入代码我是如何工作和为什么我得到了这个错误?编辑:我正在学习Data.Generics
和一般SYM,所以我想使用该模块.只有在使用该模块时我才会接受答案.谢谢.
我更熟悉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
Run Code Online (Sandbox Code Playgroud)
你是对的,showTuple
因为多态性而无效show
.问题是mkQ
想要挑选出一种特定的类型:
mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r
Run Code Online (Sandbox Code Playgroud)
将b
在类型签名必须是一个特定类型的每次使用的mkQ
-没有类型签名的缺省规则,很可能捡东西(不知道是什么!),而与它选择的类型签名Int
.
你showTupleInt
的确适用于任何大小的元组,但当然不适用于任何类型的元组.
gshows
在GHCi中定义的问题是它确实需要一个类型签名才能进行类型检查,因为gshows
它在自己的定义中以与原始调用不同的类型递归使用.没有类型签名,类型检查器希望定义gshows
与类型变量的实例完全相同gshows
- 使用- 显示为Could not deduce (a ~ d)
类型错误.
您可以通过将它放在带有和不带类型签名的源文件中来看到这一点 - 使用签名可以很好地检查它,如果你第一次使用它,你就会得到类似的错误:set -XNoMonomorphismRestriction
.
gshows
因为以下类型而起作用gmapQ
:
gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u]
Run Code Online (Sandbox Code Playgroud)
与此相反mkQ
,它所采用的参数本身就是多态的 - 请注意嵌套forall
.
虽然你showTuple
也使用了gmapQ
,但为时已晚 - mkQ
已经因为强迫show
只使用一种类型而引起了麻烦.
你也不能show
直接使用gmapQ
直接因为约束是不同的 - gmapQ
想要的东西可以在任何实例上工作Data
,而show
受到约束Show
.gshows
从来没有实际使用Show
类型类,虽然它确实使用shows
专门的String
.
在这样的情况下很难证明是负面的,但是我很确定你不能写这样的东西showTuple
会使用Show
just的多态方式使用这个类syb
,因为它根本没有任何可以"识别"类型的东西有一个特定的例子.这就是syb-with-class
存在的原因.
此外,如果你真的想要一些只适用于单一类型结构的东西,即显示任何大小的元组,但是对元组的元素使用其他东西,那么syb
可以说是错误的解决方案,因为它是为递归操作而设计的在数据结构的任何级别查找内容.我的观点是,GHC.Generics
解决方案是最好的实施方案showTuple
.
你可以使用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 ())"
-}
Run Code Online (Sandbox Code Playgroud)
gfoldl
得到调用的第二个参数shows (B 'b')
,shows (C "abc")
并且shows (B ())
由于showD dict
它获得了shows
具有正确类型的函数.