Lately I've been annoyed that Haskell has tuples with more than 2 elements. It seems to me like a great deal of boilerplate in Haskell comes from writing code like Data.Profunctor.Product. One can find many, many more examples like this in Haskell libraries.
That is to say, suppose you could define types like this:
type alias A = (x,(x,x))
or:
type alias B = ((x,x),x)
but this were banned:
type alias Triple = (x,x,x)
would you lose anything important? After all, you could still do this manually instead:
type Triple' = Triple' x x x
This would free up (a,b,c)
so that it could be used as syntax sugar instead:
(1,(2,3)) == (1,2,3)
Is this an idea that is worth exploring?
I haven't found the objections to this in say Why is (a,b,c,d) not sugar for (a,(b,(c,(d,()))))? and N-ary tuples vs pairs very compelling. The answer in the second link claims that it would weaken the type system. However, as I've demonstrated above, a manually defined type Triple'
would easily recover anything that was lost. Furthermore, I'm not advocating any changes to the type system. The following inequality would remain:
(1,(2,3)) /= ((1,2),3)
so that
(1,2,3) /= ((1,2),3)
Instead, this would be a simplification since the equality (1,2,3) == (1,(2,3))
is enforced as syntax sugar rather than a type system extension.
This would make it possible to write much simpler code if Elm happened to get say bifunctors or arrows using an operator similar to (***)
:
type MyRecord =
{ a : String
, b : Int
, c : Int
, d : [Int]
, e : String
}
let myTuple = ("Hello", "4", 8, [10.1, 9.3, 4.5], 'C')
in uncurry5 MyRecord
<| (identity *** fromString *** identity *** map ceiling *** toString)
<| myTuple
...in the future. Unfortunately infix style functions bracket in the wrong direction though, so that bimap
does not work...
type MyRecord =
{ a : String
, b : Int
, c : Int
, d : [Int]
, e : String
}
let myTuple = ("Hello", "4", 8, [10.1, 9.3, 4.5], 'C')
in uncurry5 MyRecord
<| identity
`bimap` fromString
`bimap` identity
`bimap` map ceiling
`bimap` toString
<| myTuple
-- Type error: (a,(b,(c,(d,e)))) does not match ((((a,b),c),d),e)
...never-the-less, I think at some point this sort of code will become desirable.
Also consider that something nearly recursive is now possible when it comes to defining functions like uncurry*
:
uncurry3 : (a -> b -> c -> d) -> (a,(b,c)) -> d
uncurry3 f (a,bc) = uncurry (f a) bc
uncurry4 : (a -> b -> c -> d -> e) -> (a,(b,(c,d))) -> e
uncurry4 f (a,bcd) = uncurry3 (f a) bcd
uncurry5 : (a -> b -> c -> d -> e -> f) -> (a,(b,(c,(d,e)))) -> f
uncurry5 f (a,bcde) = uncurry4 (f a) bcde
or with the syntax sugar applied to the types:
uncurry3 : (a -> b -> c -> d) -> (a,b,c) -> d
uncurry3 f (a,bc) = uncurry (f a) bc
uncurry4 : (a -> b -> c -> d -> e) -> (a,b,c,d) -> e
uncurry4 f (a,bcd) = uncurry3 (f a) bcd
uncurry5 : (a -> b -> c -> d -> e -> f) -> (a,b,c,d,e) -> f
uncurry5 f (a,bcde) = uncurry4 (f a) bcde
I could imagine some future generic programming mechanism being significantly simpler as a result.
Yeah, the
mapLeft
stuff is confusing because with your proposal tuples become linked lists, and that's hard to see from(a,b,c)
.The problem is that you can't define much because you can't tell a-priori where the tuple ends.
Like:
(a,b)
doesn't tell me its size. It just tells me that the first element is (potentially) of a different type from the second element but the second element could itself hide more stuff.The only tangible benefit I can see is that
fst
works on tuples of arbitrary size. How useful is this? I don't know. But it sounds fun. I like recursion.