Automatic conversion between tuples and record

Writing or a simple ADT in haskell is pretty much equivalent to boxed tuples. Is there a way (ideally, some fancy extensions or libs from the haksell platform) that allow conversion between this type and tuples?

I am (honestly) new to haskell, and I'm trying to create a reporting tool in Haskell. This includes reading / writing csv files and database tables. Things are pretty much straightforward using tuples, but when using a simple class, use a little boiler stove. Pattern seams are almost identical in both directions, but I did not find a good way to do this only once, with the possible exception of performing a transformation (data ↔ tuple) and using my own transformation from a tuple to a CSV / table.

Update

All the answers I have received so far suggest that I need something in common, and I want a tuple. I do not want tuples, I have a tuple, and I do not want them, so I need to convert them. In fact, I just want to reduce the boiler plate (to 0 :-)), but I do not need the function (s) to have the same name for all types.

For example, I can easily transform a tuple into something by unraveling one of its constructors. The problem is that I need uncurryN, which I cannot find anywhere (except for the haskell tutorial template). Reverse is harder to do.

I do not ask for a solution (despite the fact that all the answers I received are great because I am not familiar with the other way of metaprograms in Haskell), but more since I do not like to reinvent the wheel if the wheel already existed (for example, this uncurryN, could be written by hand until 20 and packed in nice packaging)

Updated2

There seems to be an uncurry package, but it still solves half the problem.

+7
haskell haskell-platform
source share
3 answers

You might want to check out GHC.Generics . It basically encodes each ADT as products ( (,) ) and sums ( Either ). As an example, you can show this view using generics:

 {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} import GHC.Generics class Tuple p where showRepresentation :: p -> String default showRepresentation :: (Generic p, GTuple (Rep p)) => p -> String showRepresentation = gshowRepresentation . from class GTuple p where gshowRepresentation :: px -> String instance Tuple k => GTuple (K1 ik) where gshowRepresentation (K1 t) = showRepresentation t instance GTuple f => GTuple (M1 icf) where gshowRepresentation (M1 f) = gshowRepresentation f instance (GTuple f, GTuple g) => GTuple (f :*: g) where gshowRepresentation (f :*: g) = gshowRepresentation f ++ " * " ++ gshowRepresentation g -- Some instances for the "primitive" types instance Tuple Int where showRepresentation = show instance Tuple Bool where showRepresentation = show instance Tuple () where showRepresentation = show -------------------------------------------------------------------------------- data Example = Example Int () Bool deriving Generic instance Tuple Example main :: IO () main = putStrLn $ showRepresentation $ Example 3 () False -- prints: 3 * () * False 

You can find additional documentation in the GHC.Generics module. I also found an article about this, The Universal Output Mechanism for Haskell , to be completely readable (this was one of the few articles I read).

+8
source share

The lens library, in the Control.Lens.Iso and Control.Lens.Wrapped modules, there are several utilities that make it easier to work with such transformations. Unfortunately, at the moment, the Template Haskell template for such cases does not process records, but only new types, so you have to determine the instances yourself. For example:

 {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeFamilies #-} import Control.Lens data Foo = Foo { baz :: Int, bar :: Int } deriving Show instance Wrapped Foo where type Unwrapped Foo = (Int,Int) _Wrapped' = iso (\(Foo baz' bar') -> (baz',bar')) (\(baz',bar') -> Foo baz' bar') 

Now we can easily wrap and expand:

 *Main> (2,3) ^. _Unwrapped' :: Foo Foo {baz = 2, bar = 3} *Main> Foo 2 3 ^. _Wrapped' (2,3) 

We can also change a Foo using a function that works with a tuple:

 *Main> over _Wrapped' (\(x,y)->(succ x,succ y)) $ Foo 2 5 Foo {baz = 3, bar = 6} 

And vice versa:

 *Main> under _Wrapped' (\(Foo xy)->(Foo (succ x) (succ y))) $ (2,5) (3,6) 
+3
source share

If you need real n-tuples (and not just some other data that is semantically equivalent), this would be cumbersome without Template Haskell.

For example, if you want to convert

 data Foo = Foo Int String Int data Bar = Bar String String Int Int 

in

 type FooTuple = (Int, String, Int) type BarTuple = (String, String, Int, Int) 

both GHC.Generics and SYB will be problematic because the type of the result must be different depending on the fields of the data type. Despite the fact that both are "tuples" calle, (Int, String, Int) and (String, String, Int, Int) are completely separate types, and there are no convenient ways to work with n-arity tuples in a general way. Here is one way to achieve the above using GHC.Generics :

 {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE DeriveGeneric #-} -- Generic instance to turn generic gx into some n-tuple whose exact -- type depends on g. class GTuple g where type NTuple g gtoTuple :: gx -> NTuple g -- Unwarp generic metadata instance GTuple f => GTuple (M1 icf) where type NTuple (M1 icf) = 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 ik) where type NTuple (K1 ik) = 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 ab where type Combination ab combine :: a -> b -> Combination ab -- It 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 

The above works, but it requires a lot of work (and I could not quickly figure out if this can be done without using UndecidableInstances ).

Now, what you really want to do is probably just skip the tuples and use generics to directly convert to CSV. I assume that you are using csv-conduit and want to generate instances of the ToRecord class.

Here is an example of this

 {-# 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 :: gx -> [ByteString] instance GRecord f => GRecord (M1 icf) where gToRecord = gToRecord . unM1 instance ToField k => GRecord (K1 ik) 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 

And now you can easily instantiate your custom types.

 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 

In response to your updated question: you might be interested in the tuple package (and especially Curry ), which contains implementations for uncurryN and curryN for tuples up to 15 elements long.

+1
source share

All Articles