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.
P.S. One could perhaps argue that if there were a tangible benefit to having syntax for n-ary product types in the language, it seems likely that there could be a similar benefit to n-ary sum type notation?
E.g. instead of
Either x y
notation similar to(x|y)
so that you would have...and then I guess there's all sorts of crazy places you could go from there