haskellghchaskell-prelude

Hide GHC base library to prevent pattern matching desugaring to GHC.Num.fromInteger use


I have created a replacement Prelude for use in teaching beginning Haskell students, called FirstPrelude. One of the aims is to expunge type classes from the standard library so that error messages are more of the classic Hindley-Milner variety, rather than getting No instance errors. This is working out quite well. However, something I did not anticipate is that, when pattern matching, GHC is side-stepping my redefinition of fromInteger (defined as the identity, monomorphised to only work on Integer) and so for example, with this function:

isZero 0 = True
isZero _ = False

If I ask GHCi for the type, I get:

isZero :: (GHC.Classes.Eq a, GHC.Num.Num a) => a -> Bool

But what I want is to get Integer -> Bool. Dumping the simplified core out of GHC I can see it is using:

(GHC.Num.fromInteger @Integer GHC.Num.$fNumInteger 0)))

I would have thought it would just use my fromInteger :: Integer -> Integer that is in scope, but alas no. Is there a way I can somehow prevent GHC.Num.fromInteger from being used? I guess perhaps this possible at the package level, but really I would love this at the module level for other pedagogical reasons.


Solution

  • This is not tied to pattern matching: In your Example.hs, even literals in expressions are polymorphic. To see this, write something like

    simpler :: ()
    simpler = 1
    

    and observe the error message

    [2 of 2] Compiling Main             ( Example.hs, interpreted )
    
    Example.hs:7:11: error:
        • No instance for (GHC.Num.Num ()) arising from the literal ‘1’
        • In the expression: 1
          In an equation for ‘simpler’: simpler = 1
      |
    7 | simpler = 1
      |           ^
    Failed, one module loaded.
    

    You may not have noticed because as soon as you use one of “your” operations, GHC specializes the type, and for top-level values may use defaulting.

    But note

    simpler _ = 1
    

    for which it infers this type

    ghci> :t simpler
    simpler :: GHC.Num.Num p1 => p2 -> p1
    

    If you enable {-# LANGUAGE RebindableSyntax #-}, it really uses “your” fromInteger and things work as you expect. You can do that in the .cabal file, maybe good enough for your educational needs.