idris and lean do this great thing where (a, b, c) is just sugar for (a, (b, c)), and i really wish haskell did this too. there are consequences to not doing it that way (i guess having (,..,) functions is a benefit but that could also be syntax sugar if you really wanted). i'm curious what opinions you have on this extremely important subject and if there's an even better (or worse) way of doing tuples that you've seen
(a, b, c) and (a, (b, c)) aren't isomorphic in Haskell because of laziness.
data Unit = Single
bottom :: a
bottom = error "⊥"
universeTriple :: [(Unit, Unit, Unit)]
universeTriple =
[ bottom
, (bottom, bottom, bottom)
, (bottom, bottom, Single)
, (bottom, Single, bottom)
, (bottom, Single, Single)
, (Single, bottom, bottom)
, (Single, bottom, Single)
, (Single, Single, bottom)
, (Single, Single, Single)
]
universePairPair :: [(Unit, (Unit, Unit)]
universePairPair =
[ bottom
, (bottom, bottom)
, (Single, bottom)
, (bottom, (bottom, bottom))
, (bottom, (bottom, Single))
, (bottom, (Single, bottom))
, (bottom, (Single, Single))
, (Single, (bottom, bottom))
, (Single, (bottom, Single))
, (Single, (Single, bottom))
, (Single, (Single, Single))
]
The values (bottom, bottom) and (Single, bottom) are in (Unit, (Unit, Unit)) but have no equivalents in (Unit, Unit, Unit)
Which isn't too say that having to define instances to arbitrary tuple sizes doesn't suck and cause problems and shouldn't have better language support.
Thinking about this a little more, GHC Haskell allows you to define and unpack strict data fields, so you can define the a pair type with a lazy first element and an unpacked strict second element
data LSPair a b = LSPair a {-# UNPACK #-} !b
and then
type (a, b, c) = LSPair a (LSPair b (LSPair c ()))
Naively, instance Functor ((,) a) is ambiguous with instance Functor ((,,) a b) in this model because you don't know whether the intended payload LSPair c () or LSPair b (LSPair c ()). You'd need to nest fmaps, which IMO kills possibly the only justification for tuples beyond pair in the first place. 😹
But, it's been several years since I've touched Haskell (working on fixing that lol) and there might be a way to resolve this with type families?
lol what if crimes tho
data LSPair a b = LSPair b {-# UNPACK #-} !a
type (a, b) = LSPair b (LSPair a ())
type (a, b, c) = LSPair c (LSPair b (LSPair a ()))
instance Functor (LSPair a)