如果您想要真正的n元组(而不仅仅是一些语义上等价的其他数据),没有模板Haskell将会很麻烦。
例如,如果您想要转换:
data Foo = Foo Int String Int
data Bar = Bar String String Int Int
转换为
type FooTuple = (Int, String, Int)
type BarTuple = (String, String, Int, Int)
由于数据类型的字段不同,GHC.Generics
和SYB
都会存在问题,因为结果类型需要有所不同。即使两者都被称为“元组”,(Int,String,Int)
和(String,String,Int,Int)
是完全独立的类型,并且没有方便的方法以通用方式处理n元组。以下是一种使用GHC.Generics
实现上述目标的方法:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE DeriveGeneric #-}
class GTuple g where
type NTuple g
gtoTuple :: g x -> NTuple g
instance GTuple f => GTuple (M1 i c f) where
type NTuple (M1 i c f) = NTuple f
gtoTuple = gtoTuple . unM1
newtype Single x = Single x
instance GTuple (K1 i k) where
type NTuple (K1 i k) = Single k
gtoTuple (K1 x) = Single x
class Combine a b where
type Combination a b
combine :: a -> b -> Combination a b
instance Combine (Single a) (Single b) where
type Combination (Single a) (Single b) = (a, b)
combine (Single a) (Single b) = (a, b)
instance Combine (Single a) (b, c) where
type Combination (Single a) (b, c) = (a, b, c)
combine (Single a) (b, c) = (a, b, c)
instance Combine (a,b) (c,d) where
type Combination (a,b) (c,d) = (a,b,c,d)
combine (a,b) (c,d) = (a,b,c,d)
instance (Combine (NTuple a) (NTuple b), GTuple a, GTuple b) => GTuple (a :*: b) where
type NTuple (a :*: b) = Combination (NTuple a) (NTuple b)
gtoTuple (a :*: b) = combine (gtoTuple a) (gtoTuple b)
toTuple :: (Generic a, GTuple (Rep a)) => a -> NTuple (Rep a)
toTuple = gtoTuple . from
data Foo = Foo Int String Int deriving (Generic)
data Bar = Bar String String Int Int deriving (Generic)
fooTuple = toTuple $ Foo 1 "foo" 2
barTuple = toTuple $ Bar "bar" "asdf" 3 4
上面的方法是可行的,但需要大量工作(我无法快速弄清楚是否可以在不使用
UndecidableInstances
的情况下完成)。
现在,你真正想做的可能只是跳过元组,直接使用泛型转换为CSV。我假设你正在使用
csv-conduit
,并希望生成
ToRecord
类型类的实例。
以下是一个示例:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
import Data.ByteString (ByteString)
import Data.CSV.Conduit.Conversion
class GRecord g where
gToRecord :: g x -> [ByteString]
instance GRecord f => GRecord (M1 i c f) where
gToRecord = gToRecord . unM1
instance ToField k => GRecord (K1 i k) where
gToRecord (K1 x) = [toField x]
instance (GRecord a, GRecord b) => GRecord (a :*: b) where
gToRecord (a :*: b) = gToRecord a ++ gToRecord b
genericToRecord :: (Generic a, GRecord (Rep a)) => a -> Record
genericToRecord = record . gToRecord . from
现在,您可以轻松地为自定义类型创建实例。
data Foo = Foo Int String Int deriving (Generic)
data Bar = Bar String String Int Int deriving (Generic)
instance ToRecord Foo where
toRecord = genericToRecord
instance ToRecord Bar where
toRecord = genericToRecord
作为对您更新问题的回应:您可能会对
tuple
包(特别是
Curry
)感兴趣,该包含了元组的
uncurryN
和
curryN
的实现,适用于最多15个元素的元组。
deriving (Data)
。 - luqui