Let's make Diet Haskell Haskell has tuples that can be written as

(a,b,c)


However this is just syntactic sugar for

(,,)a b c


In general an n tuple can be formed with n-1 ,s between (...) followed by its elements separated by spaces.  For example the 7-tuple, (1,2,3,4,5,6,7) can be formed by

(,,,,,,)1 2 3 4 5 6 7


Since Haskell does not have 1-tuples they cannot be formed.  You will also not be held responsible for empty tuples.

Nested tuples can be formed using parens to override the order of operations.

((1,2),3) == (,)((,)1 2)3


As part of our pursuit to remove all syntactic sugar from Haskell  I am going to ask you to write a program that removes syntactic sugar from Haskell's tuples as well.

Your program should take a tuple, an array, or a string representing a sugary tuple and should output a string representing a "sugar-free" tuple.  Input tuples will only ever contain positive integers or other tuples.

Since we are golfing here your output should be short.  It should not contain unnecessary


Spaces.  Spaces should be used only to separate arguments of a tuple functions and should not appear after a ) or before a (
Parentheses.  Parentheses should be used only when forming tuple functions or when nesting tuples.


This is a code-golf question so answers will be scored in bytes with fewer bytes being better.

Test cases

(1,2)     -&gt; (,)1 2
(1,2,3)   -&gt; (,,)1 2 3
((1,2),3) -&gt; (,)((,)1 2)3
(1,2,3,4) -&gt; (,,,)1 2 3 4
(1,(2,3)) -&gt; (,)1((,)2 3)
(10,1)    -&gt; (,)10 1
Haskell, 169 148 bytes



init.tail.fst.([]%)
p:k="(,"
l%('(':r)|(y,x:s)&lt;-[]%r,m&lt;-y:l=last$m%(p:s):[(p:p:(l&gt;&gt;k)++x:foldl(\r x-&gt;x++[' '|x&gt;k,r&gt;k]++r)[x]m,s)|x&lt;',']
l%r=lex r!!0


Try it online! Takes the tuple as a string. init.tail.fst.([]%) is the anonymous main function. Bind it to e.g. f and use like f "(3,(14,1),4,7)", which yields "(,,,)3((,)14 1)4 7".

Why is the input not provided as a Haskell tuple, you ask? Because Haskell is strongly typed, a tuple (1,2) has type (Int,Int)1 and a tuple (1,(2,3)) has type (Int,(Int,Int)). Thus a function which accepts the first kind of tuple can not be applied to the second kind, and especially there can be no function which takes an arbitrary tuple2.

Explanation:


p:k="(," is a short way to assign p to '(' and k to ",".
(%) is the recursive parsing and converting function. The first argument is a list of already parsed tuple entries, the second argument is the remainder of the original string. Each call returns a tuple of the current converted tuple (as a string and enclosed in brackets) and the remainder of the string.


l%('(':r) If the string starts with an opening bracket, we need to parse a new tuple entry.
(y,x:s)&lt;-[]%r We recursively apply % and get a tuple entry y and the remaining string splitted into the next character x and the rest of the string s.
m&lt;-y:l We add the new entry y to the current list of already found entries l and call the result m.
The next character x is now either a comma , or a closing bracket ). The last$ &lt;B&gt; :[ &lt;A&gt; |x&lt;','] is just a shorter way of writing if x == ')' then &lt;A&gt; else &lt;B&gt;.
So if a , is next, we need to recursively parse the next entry: m%(p:s) We prepend an opening bracket in order to end up in the right case and pass the list of already found entries m.
If otherwise x == ')', we finished the current tuple and need to do the required transformation: (p:p:(l&gt;&gt;k)++x:foldl(\r x-&gt;x++[' '|x&gt;k,r&gt;k]++r)[x]m,s)


p:p:(l&gt;&gt;k)++x: If we have found n entries, then m has n elements and y, the list before adding the most recently found element, has n-1 entries. This comes in handy as we need n-1 , for an n element tuple and l&gt;&gt;k works on lists as "concatenate the list k with itself as many times as y has elements". Thus this first part yields some string like "((,,,)".
foldl(\r x-&gt;x++[' '|x&gt;k,r&gt;k]++r)[x]m concatenates the elements of m (in reverse order, because by adding new entries to the front m itself was constructed in reverse order) while adding only spaces in between two elements if they are both numbers: [' '|x&gt;k,r&gt;k] we check if the current entries x and r are numbers by lexicographically comparing them to "," - if they are not numbers, they are already a tuple representation enclosed in brackets, and '(' &lt; ',' holds.

If the pattern match l%('(':r) right at the beginning fails, then we end up on the last line: l%r=lex r!!0. This means we need to parse a number and return the number and the remainder of the string. Luckily there is the lex function which does exactly that (It parses the next valid Haskell token, not just numbers). However the resulting tuple is wrapped into a list, so we use !!0 to get the first element of the list.

init.tail.fst.([]%) is the main function which takes a string and applies % with an empty list to it. E.g. for an input "(1,2)", , applying ([]%) yields ("((,)1 2)",""), so the outer tuple and brackets need to be removed. fst gets the first element of the tuple, tail removes the closing bracket and init the opening one. 


Edit: Many thanks to @Ørjan Johansen for golfing a total of 21 bytes!



1 Actually, the type is (Num t1, Num t) => (t, t1), but that's a diferent story.

2 Ignoring polymorphic functions like id, which cannot actually work with their input.