diff options
author | Ryan Scott <ryan.gl.scott@gmail.com> | 2020-03-06 11:47:56 -0500 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2020-03-11 20:34:14 -0400 |
commit | cb93a1a4405b448e83cad973f93dab3f7f050736 (patch) | |
tree | 11fecca8bf924585c8acfefa18784fa1ecdcedf7 /testsuite/tests/deriving | |
parent | a6989971379c26d8c288551d536149675e009e34 (diff) | |
download | haskell-cb93a1a4405b448e83cad973f93dab3f7f050736.tar.gz |
Make DeriveFunctor-generated code require fewer beta reductions
Issue #17880 demonstrates that `DeriveFunctor`-generated code is
surprisingly fragile when rank-_n_ types are involved. The culprit is
that `$fmap` (the algorithm used to generate `fmap` implementations)
was too keen on applying arguments with rank-_n_ types to lambdas,
which fail to typecheck more often than not.
In this patch, I change `$fmap` (both the specification and the
implementation) to produce code that avoids creating as many lambdas,
avoiding problems when rank-_n_ field types arise.
See the comments titled "Functor instances" in `TcGenFunctor` for a
more detailed description. Not only does this fix #17880, but it also
ensures that the code that `DeriveFunctor` generates will continue
to work after simplified subsumption is implemented (see #17775).
What is truly amazing is that #17880 is actually a regression
(introduced in GHC 7.6.3) caused by commit
49ca2a37bef18aa57235ff1dbbf1cc0434979b1e, the fix #7436. Prior to
that commit, the version of `$fmap` that was used was almost
identical to the one used in this patch! Why did that commit change
`$fmap` then? It was to avoid severe performance issues that would
arise for recursive `fmap` implementations, such as in the example
below:
```hs
data List a = Nil | Cons a (List a) deriving Functor
-- ===>
instance Functor List where
fmap f Nil = Nil
fmap f (Cons x xs) = Cons (f x) (fmap (\y -> f y) xs)
```
The fact that `\y -> f y` was eta expanded caused significant
performance overheads. Commit
49ca2a37bef18aa57235ff1dbbf1cc0434979b1e fixed this performance
issue, but it went too far. As a result, this patch partially
reverts 49ca2a37bef18aa57235ff1dbbf1cc0434979b1e.
To ensure that the performance issues pre-#7436 do not resurface,
I have taken some precautionary measures:
* I have added a special case to `$fmap` for situations where the
last type variable in an application of some type occurs directly.
If this special case fires, we avoid creating a lambda expression.
This ensures that we generate
`fmap f (Cons x xs) = Cons (f x) (fmap f xs)` in the derived
`Functor List` instance above. For more details, see
`Note [Avoid unnecessary eta expansion in derived fmap implementations]`
in `TcGenFunctor`.
* I have added a `T7436b` test case to ensure that the performance
of this derived `Functor List`-style code does not regress.
When implementing this, I discovered that `$replace`, the algorithm
which generates implementations of `(<$)`, has a special case that is
very similar to the `$fmap` special case described above. `$replace`
marked this special case with a custom `Replacer` data type, which
was a bit overkill. In order to use the same machinery for both
`Functor` methods, I ripped out `Replacer` and instead implemented
a simple way to detect the special case. See the updated commentary
in `Note [Deriving <$]` for more details.
Diffstat (limited to 'testsuite/tests/deriving')
-rw-r--r-- | testsuite/tests/deriving/should_compile/T17880.hs | 9 | ||||
-rw-r--r-- | testsuite/tests/deriving/should_compile/all.T | 1 |
2 files changed, 10 insertions, 0 deletions
diff --git a/testsuite/tests/deriving/should_compile/T17880.hs b/testsuite/tests/deriving/should_compile/T17880.hs new file mode 100644 index 0000000000..59662f487c --- /dev/null +++ b/testsuite/tests/deriving/should_compile/T17880.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE RankNTypes #-} +module T17880 where + +data T1 a = MkT1 (forall b. b -> (forall c. a -> c) -> a) + deriving Functor + +data T2 a = MkT2 (Int -> forall c. c -> a) + deriving Functor diff --git a/testsuite/tests/deriving/should_compile/all.T b/testsuite/tests/deriving/should_compile/all.T index e29ae0e0b5..a00617b057 100644 --- a/testsuite/tests/deriving/should_compile/all.T +++ b/testsuite/tests/deriving/should_compile/all.T @@ -122,3 +122,4 @@ test('T16518', normal, compile, ['']) test('T17324', normal, compile, ['']) test('T17339', normal, compile, ['-ddump-simpl -dsuppress-idinfo -dno-typeable-binds']) +test('T17880', normal, compile, ['']) |