1

I'm trying to convert a tuple of lists of the same length: ([Int], [Int], ..) to a list of tuples [(Int, Int, ...)]. This can be accomplished for predetermined sizes with the following code:

buildList :: ([a], [a], [a], [a], [a], [a], [a], [a]) -> [(a, a, a, a, a, a, a, a)]           
buildList ([], [], [], [], [], [], [], []) = []                                               
buildList (a:as, b:bs, c:cs, d:ds, e:es, f:fs, g:gs, h:hs) = (a, b, c, d, e, f, g, h) : buildList (as, bs, cs, ds, es, fs, gs, hs)

As you can probably see, this isn't going to be pretty when I need a lot of items in the list, and it would be much cleaner if it worked for any arbitrary value.

So my questions is, do you have a function that preforms this operation for tuples of arbitrary length?

Cactus
  • 27,075
  • 9
  • 69
  • 149
  • The tuple size defines its type, why not use list instead ? – Uri Goren Oct 31 '16 at 12:27
  • i want to extend this function for use beyond its current use, currently it is acting on a specific tuple of known size, i now have a completely different problem, and i dont want to rewrite the function – Vince Moosetaffy Oct 31 '16 at 12:29
  • 3
    You will have to rewrite the function anyway, and you should use lists instead of tuples. See also [*Why do we use tuples, if we can use a two dimensional list?*](http://stackoverflow.com/q/31497468/2751851), of which your question is a mirror image. – duplode Oct 31 '16 at 13:08
  • what would the type of such a function be? – user2297560 Oct 31 '16 at 13:10
  • the type would be ([a]) -> [(a)], but i really have no idea, i dont know very much about haskell yet. i know the benifits of lists over tuples, but i need a tuple so i can pass a function with only two parameters to a foldl that happens later. – Vince Moosetaffy Oct 31 '16 at 13:29
  • 2
    It is not a question of benefits; they are simply meant for different situations. If you have a variable number of things with the same type, you want a list. If you have a fixed number of things with different types, you want a tuple. As for the fold you mention, it is almost certain that there is a way of writing it that doesn't require building tuples of arbitrary size. You should edit the question to tell us more about that fold, as it will make your difficulty easier to grasp. – duplode Oct 31 '16 at 13:43
  • So you want `unzip`? – Thomas M. DuBuisson Oct 31 '16 at 14:37
  • 2
    Possible duplicate of [How to zip multiple lists in Haskell?](http://stackoverflow.com/questions/2468226/how-to-zip-multiple-lists-in-haskell) – gallais Oct 31 '16 at 16:59
  • Possible duplicate of [How can I implement generalized "zipn" and "unzipn" in Haskell?](http://stackoverflow.com/questions/39991581/how-can-i-implement-generalized-zipn-and-unzipn-in-haskell) – Alec Nov 01 '16 at 08:38

1 Answers1

3

As is usual with this kind of questions, the answer to 'should you' and 'can you' are different. Here, I will answer strictly the second.

The first part of solving this is a representation of n-ary tuples, or (to sound extra fancy) products. Let's do that (without using any imports so that the whole thing remains self-contained, and also because on the machine I'm currently on, Stack is misbehaving):

{-# language DataKinds, KindSignatures, TypeOperators, GADTs #-}
{-# language FlexibleInstances, FlexibleContexts #-}
{-# language TypeFamilies #-} -- This will be needed later

data Product (ts :: [*]) where
    Nil :: Product '[]
    Cons :: t -> Product ts -> Product (t ': ts)

instance Show (Product '[]) where
    show Nil = "()"

instance (Show t, Show (Product ts)) => Show (Product (t ': ts)) where
    show (Cons x xs) = let '(':s = show xs
                       in concat ["(", show x, ",", s]

So this gives us a way to write something like

*Main> myPair = Cons 1 $ Cons "Foo" $ Cons True Nil 
*Main> :t myPair
myPair :: Num t => Product '[t, [Char], Bool]
*Main> myLists = Cons [1, 2] $ Cons ["Foo", "Bar", "Baz"] Nil
*Main> :t myLists
myLists :: Num t => Product '[[t], [[Char]]]

Using this type, at least we can start thinking about what the type of our n-ary zipping function zipN should be:

zipN :: Product '[[a], [b], ...] -> [Product '[a, b, ...]]

however, we still need a way to somehow convert that tuple-of-lists Product '[[a], [b], ...] into a tuple-of-elements Product '[a, b, ...]. What I've found easiest is using an associated type family to do the conversion and the actual zipping in lockstep:

class IsLists (ts :: [*]) where
    type Unlists ts :: [*]
    zipN :: Product ts -> [Product (Unlists ts)]

instance IsLists '[] where
    type Unlists '[] = '[]
    zipN Nil = []

instance (IsLists ts) => IsLists ([t] ': ts) where
    type Unlists ([t] ': ts) = t ': Unlists ts

    -- Handling the tail is special to ensure we don't end up zipping everything
    -- with the empty list coming from zipN []
    zipN (Cons xs yss) = case yss of
        Nil -> map (`Cons` Nil) xs
        _ -> zipWith Cons xs (zipN yss)

Example:

*Main> zipN myLists
[(1,"Foo",),(2,"Bar",)]
*Main> :t it
it :: Num t => [Product '[t, [Char]]]

Note that this behaves like regular zip in that the result list's length is specified by the shortest list in the tuple.

Graham
  • 7,431
  • 18
  • 59
  • 84
Cactus
  • 27,075
  • 9
  • 69
  • 149