Если вам нужны настоящие n-кортежи (а не просто какие-то другие данные, которые семантически эквивалентны), это будет громоздко без Template 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 #-}
-- Generic instance to turn generic g x into some n-tuple whose exact
-- type depends on g.
class GTuple g where
type NTuple g
gtoTuple :: g x -> NTuple g
-- Unwarp generic metadata
instance GTuple f => GTuple (M1 i c f) where
type NTuple (M1 i c f) = NTuple f
gtoTuple = gtoTuple . unM1
-- Turn individual fields into a Single type which we need to build up
-- the final tuples.
newtype Single x = Single x
instance GTuple (K1 i k) where
type NTuple (K1 i k) = Single k
gtoTuple (K1 x) = Single x
-- To combine multiple fields, we need a new Combine type-class.
-- It can take singular elements or tuples and combine them into
-- a larger tuple.
--
class Combine a b where
type Combination a b
combine :: a -> b -> Combination a b
-- It's not very convenient because it needs a lot of instances for different
-- combinations of things we can combine.
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)
-- Now we can write the generic instance for constructors with multiple
-- fields.
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)
-- And finally the main function that triggers the tuple conversion.
toTuple :: (Generic a, GTuple (Rep a)) => a -> NTuple (Rep a)
toTuple = gtoTuple . from
-- Now we can test that our instances work like they should:
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 элементов.
person
shang
schedule
28.04.2014
deriving (Data)
- person luqui   schedule 27.04.2014