Type synonym with forall causes point-free code to not be equivalent
Summary
In GHC 9.0.2, it seems that using a point-free style doesn't
work when a certain kind of type
is involved.
This fails to compile (full details below):
limitRetries :: Natural -> RetryPolicy
limitRetries = Retry.limitRetries . fromIntegral
-- Given,
-- Retry.limitRetries :: Int -> RetryPolicy
-- type RetryPolicy = forall m. RetryPolicyM m
While this works fine:
limitRetries :: Natural -> RetryPolicy
limitRetries x = Retry.limitRetries $ fromIntegral x
As a relatively compiler-naive Haskell user, I wouldn't expect code with and without a point here to behave any differently, so I'm reporting this as a bug.
Also, HLint will automatically eta-reduce away a point, so this is a new avenue by which it can break your code.
Steps to reproduce
This error first hit me when upgrading a project using the retry
library,
so I've pared down my repro from there. I'm also using stack
because it
makes it trivial to ensure someone can run this at all the exact versions
as I'm seeing.
repro.hs
{-# LANGUAGE RankNTypes #-}
module Control.Retry
( main
) where
import Prelude
import Numeric.Natural
newtype RetryPolicyM m = RetryPolicyM { getRetryPolicyM :: () -> m (Maybe Int) }
type RetryPolicy = forall m . Monad m => RetryPolicyM m
limitRetries :: Int -> RetryPolicy
limitRetries = undefined
limitRetries' :: Natural -> RetryPolicy
limitRetries' = limitRetries . fromIntegral
main :: IO ()
main = putStrLn "hi"
% stack --resolver lts-19.3 runhaskell -- repro.hs
repro.hs:16:16: error:
• Couldn't match expected type ‘Int -> RetryPolicy’
with actual type ‘a0’
Cannot instantiate unification variable ‘a0’
with a type involving polytypes: Int -> RetryPolicy
• In the expression: undefined
In an equation for ‘limitRetries’: limitRetries = undefined
|
16 | limitRetries = undefined
| ^^^^^^^^^
repro.hs:19:17: error:
• Couldn't match type ‘c0’ with ‘RetryPolicy’
Expected: Int -> c0
Actual: Int -> RetryPolicy
Cannot instantiate unification variable ‘c0’
with a type involving polytypes: RetryPolicy
• In the first argument of ‘(.)’, namely ‘limitRetries’
In the expression: limitRetries . fromIntegral
In an equation for ‘limitRetries'’:
limitRetries' = limitRetries . fromIntegral
|
19 | limitRetries' = limitRetries . fromIntegral
Expected behavior
The code should compile.
GHC 8.10 does not exhibit this behavior:
% stack --resolver lts-18.28 runhaskell -- repro.hs
hi
With GHC 9, you have to add points to both of these:
- limitRetries = undefined
+ limitRetries x = undefined x
- limitRetries' = limitRetries . fromIntegral
+ limitRetries' x = limitRetries $ fromIntegral x
% stack --resolver lts-19.3 runhaskell -- repro.hs
hi
Environment
- GHC version used: 9.0.2 and 9.2.2
Optional:
- Operating System: Arch Linux
- System Architecture:
% uname -a
Linux prince 5.16.14-arch1-1 #1 SMP PREEMPT Fri, 11 Mar 2022 17:40:36 +0000 x86_64 GNU/Linux