问题
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?
回答1:
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.
来源:https://stackoverflow.com/questions/40342060/convert-a-tuple-of-lists-of-the-same-length-to-a-list-of-tuples-for-arbitrary