Skip to content

Premature specialization of numeric literals with Typed Holes

Summary

Numeric literals are specialized to Integer which trips up the hole fit algorithm.

Steps to reproduce

Suppose I forgot about Map.singleton, and I wrote this code:

import qualified Data.Map as Map

what :: Map.Map String Int
what = _f "hello" 1

GHC 8.10.2 will suggest mempty. The reason is that 1 gets specialized to Integer, which means that _f :: String -> Integer -> Map String Int, and there are no valid fits for it. We can fix this by either changing Int to Integer or adding a type annotation to 1.

If we generalize the Map, this still doesn't work:

what :: forall a. Num a => Map.Map String a
what = _f "hello" 1
/home/matt/map.hs:6:8: error:
    • Found hole: _f :: [Char] -> Integer -> Map.Map String a
      Where: ‘a’ is a rigid type variable bound by
               the type signature for:
                 what :: forall a. Num a => Map.Map String a
               at /home/matt/map.hs:5:1-43
      Or perhaps ‘_f’ is mis-spelled, or not in scope
    • In the expression: _f
      In the expression: _f "hello" 1
      In an equation for ‘what’: what = _f "hello" 1
    • Relevant bindings include
        what :: Map.Map String a (bound at /home/matt/map.hs:6:1)
      Constraints include Num a (from /home/matt/map.hs:5:1-43)
      Valid hole fits include
        mempty :: forall a. Monoid a => a
          with mempty @([Char] -> Integer -> Map.Map String a)
          (imported from ‘Prelude’ at /home/matt/map.hs:1:1
           (and originally defined in ‘GHC.Base’))
  |
6 | what = _f "hello" 1
  |        ^^

The only way to get the right suggestion is to force 1 to remain polymorphic with a type signature.

{-# language ScopedTypeVariables #-}

import qualified Data.Map as Map

what :: forall a. Num a => Map.Map String a
what = _f "hello" (1 :: a)
/home/matt/map.hs:6:8: error:
    • Found hole: _f :: [Char] -> a -> Map.Map String a
      Where: ‘a’ is a rigid type variable bound by
               the type signature for:
                 what :: forall a. Num a => Map.Map String a
               at /home/matt/map.hs:5:1-43
      Or perhaps ‘_f’ is mis-spelled, or not in scope
    • In the expression: _f
      In the expression: _f "hello" (1 :: a)
      In an equation for ‘what’: what = _f "hello" (1 :: a)
    • Relevant bindings include
        what :: Map.Map String a (bound at /home/matt/map.hs:6:1)
      Constraints include Num a (from /home/matt/map.hs:5:1-43)
      Valid hole fits include
        Map.singleton :: forall k a. k -> a -> Map.Map k a
          with Map.singleton @String @a
          (imported qualified from ‘Data.Map’ at /home/matt/map.hs:3:1-32
           (and originally defined in ‘Data.Map.Internal’))
        mempty :: forall a. Monoid a => a
          with mempty @([Char] -> a -> Map.Map String a)
          (imported from ‘Prelude’ at /home/matt/map.hs:1:1
           (and originally defined in ‘GHC.Base’))
  |
6 | what = _f "hello" (1 :: a)
  |        ^^

Curiously, this also fails to suggest Map.singleton:

{-# language AllowAmbiguousTypes, ScopedTypeVariables #-}

import qualified Data.Map as Map

what :: forall a. Num a => Map.Map String Int
what = _f "hello" (1 :: a)

It can't unify _f :: String -> a -> Map.Map String Int with Map.singleton :: String -> Int -> Map.Map String Int.

I tried this with mempty instead of 1, but that actually works:

import qualified Data.Map as Map
import Data.Monoid (Sum)

what :: Map.Map String (Sum Int)
what = _f "hello" mempty
[1 of 1] Compiling Main             ( /home/matt/map.hs, interpreted )

/home/matt/map.hs:5:8: error:
    • Found hole: _f :: [Char] -> t0 -> Map.Map String (Sum Int)
      Where: ‘t0’ is an ambiguous type variable
      Or perhaps ‘_f’ is mis-spelled, or not in scope
    • In the expression: _f
      In the expression: _f "hello" mempty
      In an equation for ‘what’: what = _f "hello" mempty
    • Relevant bindings include
        what :: Map.Map String (Sum Int) (bound at /home/matt/map.hs:5:1)
      Valid hole fits include
        Map.delete :: forall k a. Ord k => k -> Map.Map k a -> Map.Map k a
          with Map.delete @String @(Sum Int)
          (imported qualified from ‘Data.Map’ at /home/matt/map.hs:1:1-32
           (and originally defined in ‘Data.Map.Internal’))
        Map.singleton :: forall k a. k -> a -> Map.Map k a
          with Map.singleton @String @(Sum Int)
          (imported qualified from ‘Data.Map’ at /home/matt/map.hs:1:1-32
           (and originally defined in ‘Data.Map.Internal’))
        seq :: forall a b. a -> b -> b
          with seq @[Char] @(Map.Map String (Sum Int))
          (imported from ‘Prelude’ at /home/matt/map.hs:1:1
           (and originally defined in ‘GHC.Prim’))
  |
5 | what = _f "hello" mempty
  |        ^^

/home/matt/map.hs:5:19: error:
    • Ambiguous type variable ‘t0’ arising from a use of ‘mempty’
      prevents the constraint ‘(Monoid t0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘t0’ should be.
      These potential instances exist:
        instance Num a => Monoid (Sum a)
          -- Defined in ‘base-4.14.1.0:Data.Semigroup.Internal’
        instance Ord k => Monoid (Map.Map k v)
          -- Defined in ‘Data.Map.Internal’
        instance Monoid a => Monoid (IO a) -- Defined in ‘GHC.Base’
        ...plus 9 others
        ...plus 22 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the second argument of ‘_f’, namely ‘mempty’
      In the expression: _f "hello" mempty
      In an equation for ‘what’: what = _f "hello" mempty
  |
5 | what = _f "hello" mempty
  |                   ^^^^^^
Failed, no modules loaded.

Surprisingly enough, it suggests seq, which relies on the Monoid instance for Map! But it doesn't suggest mempty which should fit with mempty @(_ -> Map _ _) (via Monoid r => Monoid (x -> r)).

Expected behavior

Instead of specializing 1 :: Num a => a to Integer and then searching for hole fits, search for hole fits with the general signature. eg _f :: Num a => String -> a -> Map String Int. Map.singleton (as a valid specialization of the typed hole) should fit.

Environment

  • GHC version used: 8.10.2

Optional:

  • Operating System:
  • System Architecture:
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information