Skip to content

Eta-expansion can cause binders in callers to become lazier.

Consider this code:

foo !x = ...

bar =
   let x = <thunk>
   in foo x

Given that foo is strict in it's first arg one would hope that bar eventually compiles down to code such as:

bar =
   case <thunk> of x
     _DEFAULT -> foo x

And that's generally what happens. However if we eta-expand foo instead we get:

foo !x y = ...

bar =
   let x = <thunk>
   in \y -> foo x y

This happens since now only once bar is applied to an additional argument x will be evaluated. This can be good or bad depending on the code in question, but it's definitely surprising.

I observed this happening in #22425 (closed)

I think this has a potential bad interaction with when the simplifier drops seqs that I haven't yet seen in the wild.

In particular we might start out with:

-- foo has arity 1
bar x =
   case x of x' -> foo x'

-- The simplifier sees that foo is strict in x' and will drop the seq
-- foo has arity 1
bar x =
   foo x'

-- We eta expand foo
-- foo has arity 2
bar x =
   \y -> foo x' y

And suddenly forcing bar x no longer forces x despite the user potentially having explicitly written it to do so. But as I said I haven't observed the later part in the wild yet and haven't tried to come up with a reproducer. I doubt it happens often in practice.

@sgraf812 You might find this interesting/have more to say about this.

Edited by sheaf
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information