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.
On mathematical aggregates like vectors etc
This is a bit of a side note, but a lot of people are mentioning math vectors for tuples versus records. I want to play devil's advocate a little bit because I must admit for 3D vectors, 4x4 matrices etc when the math gets hairy I often prefer having numeric indexes around for indexing those object.
Indexing into math objects
E.g. using
Array
, so that I can write the code:...that kind of code can get quite annoying if you have to write
case axisIndex of
to do the math.Data packing / performance etc
Also, I wonder if records would have an efficient enough representation to use for 3D graphics. It seems to me that if you construct a large array of 3D vectors they should be ideally be compiled into something like TypedArray or similar.
Anyway, I'm not sure what the underlying representation for
{ x: 1, y: 0, z: 0 }
looks like versus[1,0,0]
in JavaScript, or even what(1,0,0)
looks like for Elm?To make it more concrete, how should one represent a 4x4 matrix efficiently? I imagine a tuple is probably as efficient as it gets?
E.g. that
...could (in future) compile down to
...in javascript ideally.
Constructing new, larger aggregate objects
It's also often convenient to construct matrices out of basis vectors for example, which is more cumbersome with records. E.g.