Skip to content

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
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information