I always thought that replacing an expression x :: ()
with () :: ()
would be one of the most basic optimizations during compiling Haskell programs. Since ()
has a single inhabitant, no matter what x
is, it's result is ()
. This optimization seemed to me as a significant consequence of referential transparency. And we could do such an optimization for any type with just a single inhabitant.
(Update: My reasoning in this matter comes from natural deduction rules. There the unit type corresponds to truth (⊤) and we have an expansion rule "if x : ⊤
then () : ⊤
". For example see this text p. 20. I assumed that it's safe to replace an expression with its expansion or contractum.)
One consequence of this optimization would be that undefined :: ()
would be replaced by () :: ()
, but I don't see this as a problem - it would simply make programs a bit lazier (and relying on undefined :: ()
is certainly a bad programming practice).
However today I realized that such optimization would break Control.Seq
completely. Strategy
is defined as
type Strategy a = a -> ()
and we have
-- | 'rseq' evaluates its argument to weak head normal form.
rseq :: Strategy a
rseq x = x `seq` ()
But rseq x :: ()
so the optimization would simply discard the required evaluation of x
to WHNF.
So where is the problem?
- Does the existence of
rseq
andseq
break referential transparency (even if we consider only terminating expressions)? - Or is this a flaw of the design of
Strategy
and we could devise a better way how to force expressions to WHNF compatible with such optimizations?
x
may not terminate, then replacing it by()
can change the meaning of the programme.main = case last (repeat ()) of { () -> launchMissiles }
. – EpithetStrategy
) relies on_|_ :: ()
and I doubt it's reasonable to do so. Also the problem withrseq
+ the optimization occurs even if we consider only terminating expressions. – Properlyx
terminates, replacingrseq x
with()
doesn't change the meaning of the programme. That replacement changes the meaning if and only ifx
doesn't terminate. I can't give a meaningful example programme relying on()
being inhabited by ⊥ directly, though. But it's the point of the magicalseq
that such a substitution is not made.seq
is somewhat at odds with the rest of the language, but it is too useful to turn space-and-time-leaks into working programmes to live without it. That's why it is in the language despite its magic. – Epithet_|_ :: ()
not being a meaningful, real program. (I'm guessing you don't mean that it lacks meaning/semantics and that it doesn't exist.) – RuralStrategy
orseq
of course) whose meaning would change if we allowed the rulex :: () --> () :: ()
. Or, stated in a broader context: What would we lose if we made all pattern matching on single-constructor data types automatically lazy? Would this semantic change cause some programs to be harder to write? (This change would imply the optimization rule - like Daniel's example would becomecase last (repeat ()) of { ~() -> launchMissiles }
which would always launch missiles.) – Properly