4

In Haskell we can flatten a list of lists Flatten a list of lists

For simple cases of tuples, I can see how we would flatten certain tuples, as in the following examples:

flatten :: (a, (b, c)) -> (a, b, c)
flatten x = (fst x, fst(snd x), snd(snd x))

flatten2 :: ((a, b), c) -> (a, b, c)
flatten2 x = (fst(fst x), snd(fst x), snd x)

However, I'm after a function that accepts as input any nested tuple and which flattens that tuple.

Can such a function be created in Haskell?

If one cannot be created, why is this the case?

user65526
  • 685
  • 6
  • 19
  • 3
    Yes, use pattern matching. Write a function `(a, (b, c)) -> (a, b, c)`. – Mark Seemann Aug 14 '20 at 14:37
  • 1
    What exactly do you mean by ‘generalis[ing] to the case of an arbitrary n-tuple’? If you mean, ‘can we write a function which will accept as input _any_ nested tuple and flatten it’, then no, such a function is impossible (or at least impractical) to write as far as I’m aware – bradrn Aug 14 '20 at 15:03
  • 1
    @Bradm Yes, that is precisely what I meant. A function that can accept as input any nested tuple and flatten it. – user65526 Aug 14 '20 at 15:06
  • @Bradm I have edited my question to include your preciser way of formulating what I had in mind. – user65526 Aug 14 '20 at 15:07
  • I think [this](https://stackoverflow.com/a/16055362/2550406) may be related. I haven't used haskell for too long though – lucidbrot Aug 14 '20 at 15:53
  • 2
    Side note: the functions you have there are much nicer to read and write if you use pattern matching rather than nested calls to `fst` and `snd`: `flatten (a, (b, c)) = (a, b, c)` – Robin Zigmond Aug 14 '20 at 16:10
  • 1
    Is there a larger context to the actual problem you're trying to solve? A heterogenous list might be a better choice than tuples. – David Young Aug 14 '20 at 22:41

1 Answers1

11

No, it's not really possible. There are two hurdles to clear.

The first is that all the different sizes of tuples are different type constructors. (,) and (,,) are not really related to each other at all, except in that they happen to be spelled with a similar sequence of characters. Since there are infinitely many such constructors in Haskell, having a function which did something interesting for all of them would require a typeclass with infinitely many instances. Whoops!

The second is that there are some very natural expectations we naively have about such a function, and these expectations conflict with each other. Suppose we managed to create such a function, named flatten. Any one of the following chunks of code seems very natural at first glance, if taken in isolation:

flattenA :: ((Int, Bool), Char) -> (Int, Bool, Char)
flattenA = flatten

flattenB :: ((a, b), c) -> (a, b, c)
flattenB = flatten

flattenC :: ((Int, Bool), (Char, String)) -> (Int, Bool, Char, String)
flattenC = flatten

But taken together, they seem a bit problematic: flattenB = flatten can't possibly be type-correct if both flattenA and flattenC are! Both of the input types for flattenA and flattenC unify with the input type to flattenB -- they are both pairs whose first component is itself a pair -- but flattenA and flattenC return outputs with differing numbers of components. In short, the core problem is that when we write (a, b), we don't yet know whether a or b is itself a tuple and should be "recursively" flattened.

With sufficient effort, it is possible to do enough type-level programming to put together something that sometimes works on limited-size tuples. But it is 1. a lot of up-front effort, 2. very little long-term programming efficiency payoff, and 3. even at use sites requires a fair amount of boilerplate. That's a bad combo; if there's use-site boilerplate, then you might as well just write the function you cared about in the first place, since it's generally so short to do so anyway.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • Ha, I was in the process of writing my own answer using type classes and functional dependencies, and have literally just run into precisely this issue with overlapping instances while testing it out! – Robin Zigmond Aug 14 '20 at 16:32
  • @Daniel Wagner I'm going to ask a question on mathstackexchange or elsewhere about whether it is in principle possible in certain lambda calculi. It also interests me whether it is theoretically possible, outside Haskell. – user65526 Aug 14 '20 at 16:33
  • 1
    @user65526 If your calculus has any polymorphism, then it is going to come up against the second hurdle. In a monomorphic calculus, there's no problem with simply adding a term to the syntax that does this (and implementing it as compiler magic, say), though I would not expect it to be a thing that could be implemented in the language... because, well, no polymorphism, right? I suspect you will need to make it all the way to full dependent types before there's a sensible way of implementing it yourself as a library function, but with all the runtime type information consequences of dependence. – Daniel Wagner Aug 14 '20 at 16:35
  • 1
    @DanielWagner, I suspect there's a Haskell equivalent to that dependent thing: requiring that all the components (recursively) be `Generic` (and more; `Typeable` is one option for the more). Then you can play games with things like the names of the (type or data) constructors. – dfeuer Aug 14 '20 at 17:25
  • 1
    Oh, but `Generic` instances for tuples don't go very high, for stupid compiler performance reasons. *Sigh* – dfeuer Aug 14 '20 at 17:56
  • @Daniel Wagner, but it is possible and has already been done. https://hackage.haskell.org/package/tuple-morph-0.1.0.0 – RowanStone Mar 08 '21 at 09:30
  • @RowanStone Uh... no it hasn't? [`sizeLimit`](https://hackage.haskell.org/package/tuple-morph-0.1.0.0/docs/Data-Tuple-Morph.html#v:sizeLimit), the size of the largest tuple this library will work with. – Daniel Wagner Mar 08 '21 at 12:44
  • @Daniel Wagner, for all practical purposes it is, and as I quickly browsed the source code nothing stops you from increasing this limit, nothing except from long compile times, I think. – RowanStone Mar 09 '21 at 14:13