Okay, okay. This is probably ground well-trodden decades ago by the Wise Ones of the Mailing Lists, if it isn’t so obvious that the Wise Ones never bothered to verbalize it, but here’s what I’m currently thinking. Epistemic status: I am a filthy pleb and I’ve been changing my mind about all this like twice a day.
Claim 1: A categorical product in a category with lifted types should not itself be lifted.
For my fellow plebs, a categorical product is defined by a unique way to combine given functions f : A → B and g : A → C to form a function 〈f, g〉 : A → B × C that correctly composes with projections. Assume f and g are chosen so that there is some a for which f(a) = g(a) = ⊥. If B × C is a lifted type, there are two valid ways to take the product of f and g:
- 〈f, g〉(x) = (f(x), g(x)) (the usual way)
- 〈f, g〉(x) = if x = a then ⊥ else (f(x), g(x))
We know these are distinguishable functions because ⊥ is distinguishable from (⊥, ⊥).
As a practical matter, making tuple types and Product
literally unlifted types in Haskell seems like a lot of hassle.
Claim 2: The next best thing to using an unlifted product type is using the lifted pseudoproduct type but with a gentleperson’s agreement to consider ⊥ as equivalent to (⊥, ⊥).
This is a wishy-washy claim, but note that the above argument is no longer valid if we decide to ignore the difference between ⊥ and (⊥, ⊥). The projection functions fst and snd are constrained by monotonicity to map ⊥ to ⊥, so there is no other value we could consider as a candidate for equivalence to ⊥ that would rescue the categorical product.
We can compose any function that in its Platonic form would like an unlifted product type with the tuple-expansion function λx. (fst x, snd x), and any unlifted product type returned by a function will trivially embed in the lifted type.
Claim 3: A lazy tuple pattern is equivalent to composing with the tuple-expansion function.
I think this is straightforward.
Taken all together, this leads me to believe that to the extent that we want to think of tuples and Product
s as things with the categorical-product nature, we should make every tuple/Product
pattern lazy if it makes any observable difference at all. Any law violations that rely on the difference between ⊥ and (⊥, ⊥) should be excused. If this conflicts with engineering concerns, we should trade those off against wanting to be able to reason abstractly about products.
My picture of what the engineering concerns are is still very fuzzy (Doesn’t GHC do a fair amount of strictness analysis, and doesn’t that mean that many lazy patterns end up being strictified? But when does this fail, and what other types of problems are lurking?), but at least I think I have a better idea of what the ideal would be sans those concerns.
The odds that someone will change my mind on this in half a day are still pretty high though, so I’m posting this here and not on the CLC thread so I don’t end up wasting the CLC’s time.