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.
Good point. I don't know if you'd want to change the name really, because it would get confusing with something like
Bifunctor
where the type could be either a product or sum(I renamed Haskell's
first
tomapFirst
andsecond
tomapSecond
to be more "Elm-ish")This is silly, but maybe?
or preceding and succeeding
These are more agnostic towards the number of items in the tuple I think. Unfortunately I'm sure all those sorts of ideas clashes with one programming language or another.
Then there's also a much more crazy idea which I'm sure will be shot down quickly because I can't imagine it would fit into a super simple typeclass:
It would be really nice to map this way though
I always thought it silly that sum types have a direction associated to them (left and right) but product types have a number (first and second) - that's a little bit arcane! Actually the directions make more sense to me on products than sums, at least with a pair the left-hand side is visually on the left.
I propose this new definition for
Either
using colors instead:(I'm just kidding)