Skip to content

Use "eta-expand" for returned newtypes

Consider

newtype T = MkT (Bool -> Char)

f :: Int -> T
f = \x. ((\y::Bool. blah) |> co)

where co :: (Bool -> Char) ~ T. Then the simplifer decides that f has arity 2, and (in SimplUtils.tryEtaExpandRhs) uses CoreArity.etaExpand to eta-expand to:

f = (\x y. blah) |> (Int -> co)

We move the cast to the outside, to allow the lambdas to come together. Then Note [Float coercions] in Simplify.hs will transform to

f2 = \x y. blah
f = f2 |> (Int -> co)

and f will be inlined at every call site. Since the calls may well look like ((f 3) |> co) True, inlining f = f2 |> (Int->co) will eliminate a lot of cast clutter.

But this only applies if there is a cast at the top, or in the middle. Suppose the cast was at the end:

newtype N = MkN Int

g :: Int -> N
g = \x -> ...

then we do no eta expansion because g already has manifest arity 1. But it might actually make sense to expand to

g :: Int -> N
g = (\x -> ... |> sym co ) |> (Int -> co)

because then the Note [Float coercions] in Simplify.hs will transform to

g2 :: Int -> Int
g2 = \x -> ... |> sym co

g :: Int -> N
g = g2 |> (Int -> co)

and now (even if g is recursive) we'll inline g everwhere. That in turn may mean that casts are eliminated.

It also applies for non-functions, with arity zero:

h :: N
h = case <blah> of
      True -> 3 |> co    -- co :: Int ~ N
      False -> 4 |> co

We might want to transform to

h2 :: Int
h2 = (....same code...) |> sym co    -- sym co :: N ~ Int

h :: N
h = h2 |> co

It's a simple form of worker/wrapper (as #17673 (closed) observes) but a useful one. I doubt it'll make any programs run faster, but it'll eliminate cast clutter. Moreover, it seems consistent with what we do for other casts.

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